Monday, November 3, 2008

WPF & Multithread - Part 2 and creating multiple windows on different messaging threads

In my previous post I talked about getting input from a user while running on a business thread. You maybe wondering what happens if I choose to just always create a new STA thread rather than check to see if the calling thread is already on an STA thread. After all, that's less code.

Well, that's what I originally did, until I ran into a problem. You see, it all comes down to calling the Thread.Join call. This call blocks the calling thread. So, if the calling thread is the UI thread, you've just blocked it! Oops.. there goes the message pumping for that thread. So, say you have a timer on your main window. Well, suddenly it comes to a stop. In my case, I had a custom NativeWindow class that listens for keystrokes across the application to detect incoming barcode values from a barcode scanner. For my application it is essential the WndProc override of my NativeWindow always gets called. This is because I use the Raw Input API to filter keyboard input searching for barcode values. However, the Raw Input API stops all WM_KEYxx calls from being generated. The only way any of my WPF windows will get a message (and hence keystrokes) is if I manually send the WM_KEYxx messages. So, as you can see, it is essential to the application that the NativeWindow.WndProc method is always called.

But as soon as I pop up my new WPF window to ask the user a question, they can no longer type. That's because the new window is running on the newly created STA thread and the original UI thread (and the one running the NativeWindow.WndProc) is now blocked! Users don't need to type, do they?

So, in my original post I showed a simple way to deal with this. Just check the calling thread and see if it is a UI thread. If so, show the Dialog box on that thread and you're good to go.

However, another option that also works is to create my NativeWindow on a different thread. The key is to call at the end System.Windows.Forms.Application.Run(). This method basically runs the old fashion message loop.. remember that one?



while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}


Of course, the reason I choose not to do this method was it was just too much work! First of all, now I need to worry about multiple message loops, and more cross thread issues. Secondly, an Application.Current.Shutdown() call from my WPF window doesn't shutdown the application. That's because you need to somehow communicate to the newly created window to shutdown it's thread. So, more work...

Anyway, here's a simple example of creating a new window on a different thread, so the message loop is on a different thread. I am not saying this is the way to do things, but I wrote about it because it was a good find and maybe used later..




public void Startup ()
{
Thread NewThread = new Thread(new ThreadStart(CreateWindow));
NewThread.SetApartmentState(ApartmentState.STA);
NewThread.Start();
}

private void CreateWindow()
{
// Create a new native window that will be hidden.
CreateParams cp = new CreateParams();
cp.Caption = "MyWindow";
cp.ClassName = null;
cp.Style = 0x08000000 | 0x20000000;
cp.Height = 500;
cp.Width = 500;

this.CreateHandle(cp);
CreateRegistration();

// Here is the key! Application.Run encapsulates the old standard message loop.
Application.Run();
}

1 comment:

  1. I also wanted to note how to do this in a WPF application. See this
    blog.

    ReplyDelete