Hi James,
I though about the comment you made about having two separate thread
one for the form and the message handler running on a separate thread.
You stated that that would make no difference since the message
handling function in the form would not allow any posting of messages
from the form until the form completes its message handling work.
Yes, if you notify the form with PostMessage. I tought this was your idea
since you wanted to have the handler of the form.
But cant we make the message handling function in the form to be
defined as a function delegate and from the message handler class we
could execute this function, and hence would be executed on the
message handlers thread so the form would be free to do what ever it
wants. This is code. Your comments are welcome.
Yes that is perfectly right. However, bare in mind that the message handling
function will be executed by a thread different from the one created the
form. this means that if you want to manipulate the form or any controls on
the form you need to call Form.Invoke method and switch the execution in the
form's UI thread, which if you do for all message processing you will get
almost what we have with the PostMessage notification
Ok. I think I got your idea so I'll post some working code. I'll make two
posts. One is using NativeWindow as a message handler and executing the
message processing function in the MessageHandler's thread.
However I'm little worried about the fact that we create an UI thread, which
is more expensive that the normal worker thread and a window. I'm not sure
if it's worth. Then I'll make a second post with a working example that do
the same thing but uses only a worker thread. I would recomend the second
one.
The first code (using the NativeWindow) follows. At the end of the code
there are some well-known problems that have to be taken care of.
---------------- Form class -------------------------------
using System;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;
using System.Threading;
namespace TestMessageHandler
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
private System.Windows.Forms.Button button1;
private CMessageHandler msg_handler;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
Thread t = new Thread(new ThreadStart(InitializeMessageHandler));
t.Start();
}
private void InitializeMessageHandler()
{
msg_handler = new CMessageHandler(new CMessageDelegate(DecodeMessage));
//runns the message loop in the current thread.
Application.Run();
}
void DecodeMessage(string msg)
{
Console.WriteLine(msg);
Thread.Sleep(3000); //simulate long processing
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(112, 224);
this.button1.Name = "button1";
this.button1.TabIndex = 0;
this.button1.Text = "Post";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
public void PostMessage(string msg)
{
msg_handler.Enqueue(msg);
}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private int mMessageNum = 0;
private void button1_Click(object sender, System.EventArgs e)
{
this.PostMessage(string.Format("Message #{0}", mMessageNum++));
}
}
}
----------------------- MessageHandler class ----------------------------
using System;
using System.Collections;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace TestMessageHandler
{
public delegate void CMessageDelegate(string msg);
public class CMessageHandler : System.Windows.Forms.NativeWindow
{
[DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool PostMessage(System.Windows.Forms.Message
Msg);
private Queue queue;
private CMessageDelegate decodeMessage;
public CMessageHandler(CMessageDelegate decodeMessage)
{
queue = new Queue();
this.decodeMessage = decodeMessage;
CreateParams cp = new CreateParams();
this.CreateHandle(cp);
}
public void Enqueue(string message)
{
queue.Enqueue(message);
System.Windows.Forms.Message msg =
System.Windows.Forms.Message.Create((IntPtr)this.Handle,1234,(IntPtr)0,(IntP
tr)0);
PostMessage(msg);
}
private string GetMessage()
{
return (string)queue.Dequeue();
}
protected override void WndProc(ref Message msg)
{
if (msg.Msg == 1234)
{
this.decodeMessage(GetMessage());
}
base.WndProc(ref msg);
}
}
}
Problems:
----------
The form has to destroy the MessageHandler when it's closing.
This could be not so easy, though. Imagine that you have several messages
waitng in the queue to be processed.
If you close the form the handler will keep sending the messages for
processing. So you might end up with disposed form in the middle of
processing a message.
Destroying the window should be done by calling NativeWindow object
DestroyHandle method. Internaly, I believe, it uses DestroyWindow API call.
In MSDN you can read about that API:
"...A thread cannot use DestroyWindow to destroy a window created by a
different thread. ...."
That's why you can't destroy the MessageHandler from inside the form's UI
thread.
You might have some special flag or message for closing the handler. Put
that message at the queue's head because otherwise you will have to wait for
the all messages in the queue to be processed and then the MessageHandler
will be shuted down. Of course it could be what exactly you are going for.
Either ways the form should wait the MessageHandler to be destroyed and then
it can proceed with its own closing. Otherwise, how I said, you may end up
with disposed form in the middle of a message processing.
In my next post I'll give you my other solution, which I BTW would recomend
because it is cheaper resource wise.
HTH
B\rgds
100