Wednesday, November 19, 2008

Visual Studio 2008's bug in it's XAML editor bugged me all morning

All I got to say is "AAAAHHH!"...

I spent the morning trying to figure out why Intellisense fails to work in my XAML files. Life was great, then one day it stopped working. I couldn't figure out why. Googling for solutions came up with nothing.

So, I decided to create a blank XAML file in my project. Guess what? Intellisense worked there. So, I went through starting to recreate my "bad" XAML file. As soon as I added a namespace reference to my own assembly, it broke. In the code snippet below, you'll see the offending line. It's the xmlns:local. Remove that line, Intellisense works, add it and Intellisense breaks. Great!



<Window x:Class="MyProject.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject">



Now that I knew it was related to that line, a much more targeted google brought up this one Microsoft forum where the issue was discussed, recognized as a bug by MS, and supposed will be fixed in the next Service Pack. Problem was, at the time of writing, the next SP was to be SP1. I've got SP1, but the bug still persists.

So, in light of the stupid bug, this is the workaround I'm using. It's not perfect, but if you use a reference to your local assembly only for a model-view, you can push your model-view off into a resource file then link via a shared dictionary to your actual UI.

SharedResources.xaml:



<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:MyProject"
>
<!-- Your local resources go here.. oh, and Intellisense is broken when editing this file -->
</ResourceDictionary>


And now the actual window...



<Window x:Class="MyProjects.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="SharedResources.xaml"/>
<ResourceDictionary>
<!-- Any local resources -->
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>

<!-- Your contents -->
</Window>


Sometimes you make coding decisions just because you like your IDE too much to give up it's tools...

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();
}

Multithreading and WPF Windows

I recently had an interesting problem come up with UI threads in .NET applications and how input is processed. Here's the basic rundown...

A user initiates an event in the UI on Thread 1

The UI makes a call to some business layer to process the request. Note, this MAY or MAY NOT continue to execute on the UI thread.

The business logic needs more information before it can proceed. Nicely separated, the business thread raises an event called GetAdditionalInfo. Now, the application can either provide the information directly back to to the business logic or it may need to ask the user.

Now, here's where things get "interesting". If the business logic is running on a different thread, you've got a problem. You can't simply create a new WPF window to ask the user for the information. If you try, you'll get the following error message:

System.InvalidOperationException: The calling thread must be STA, because many UI components require this.

Well, what are you to do? A few options come up.

One option is to put a message onto the dispatch thread for the current window. While this method will definitely get your message across, you have a bit of a problem. That is, the business logic thread will continue to execute Problem is, you need to hold up the business logic thread until the user has responded to the question.

The trick lies in spawning off a new thread, creating the WPF window in that new thread, and block the business logic thread until the UI finishes. Of course, if the business logic thread is already a UI thread, this isn't needed and you can just do a ShowDialog directly. Code below...



private static void ShowWPFDialog(object arg)
{
GetAdditionalInformationWPFWindow myWindow = new GetAdditionalInformationWPFWindow();

// ShowDialog will block the calling thread..
myWindow.ShowDialog();

// Presumably at this point you have the data required from the UI... So you could set it in the arguments..
((BusinessRequestEventArgs)args).MyResult = myWindow.BusinessLogicResult;
}

private static void GetAdditionalInformation(object sender, BusinessRequestEventArgs e)
{
// If the current thread is already an STA thread we can just run on this thread.
if (Thread.CurrentThread.ApartmentState == ApartmentState.STA)
RetrieveSecondaryLogon(arg);
else
{
Thread _UIThread;
_UIThread = new Thread(new ParameterizedThreadStart(RetrieveSecondaryLogon));
_UIThread.SetApartmentState(ApartmentState.STA);
_UIThread.Start(arg);
// Block the caller until the UI thread ends..
_UIThread.Join();
}
}