Dec 19, 2010

Yet another TaskDialog wrapper, this time for WPF!

There are a lot of .NET projects out there for adding TaskDialog support for your Windows applications. They can do this (relatively) easily enough by simply wrapping a lot of calls and types to the Win32 API directly, most importantly the TaskDialogIndirect function and its related TASKDIALOGCONFIG data structure.

Until Vista, the always abused MessageBox incarnation (having been around in some form since VB classic days) was the easiest way to get native message dialogs in your app. Sure, you could always write your own window dialog class and use that, but it was a lot of work for what could alternatively be a single method call. Of course, MessageBox had significant limitations that dogged you at every step of UI design. But it did have the benefit of being OS native and standardized and it would be guaranteed to work in just about any version of Windows your program might run on. Plus, things like the icons would be specific to the version of Windows, too, further keeping with the native look and feel (which, in my opinion, is usually a plus).

Along came the Task Dialog in Vista and with it new guidelines on how and why to prompt the user. It also greatly expanded on what was possible to show in a standardized and native fashion. Things like progress bars, command links, radio button choices, "don't show this again", expanded details hidden by default, and so much more were now built-in.

Unfortunately for .NET Windows developers, even though .NET 4.0 is out and it’s been roughly four years since Vista and its Task Dialog were introduced, MessageBox is still the only native dialog accessible out-of-the-box in managed framework classes. Maybe in the next version? Who knows. It's important that they get it in there eventually, because it'll be one more way to make Task Dialogs more prevalent in Windows apps. This will be, on the whole, a good thing, since Task Dialogs are a much better user interaction mechanism than the simpler MessageBox or many home-brewed custom jobs.

Besides the fact that it isn't exposed normally, forcing everyone to roll their own wrapper, Task Dialogs don't work in XP or earlier. One nice thing about writing Windows apps in .NET is that for the most part anything you do will pretty much work the same on any version of Windows [that supports your target version of .NET]. Obviously when you start using P/Invoke voodoo that nice cross-version-ness goes out the window. This is probably the main reason why .NET doesn't yet provide Task Dialogs.

Still, though, WPF has already gone to the trouble of emulating the look and feel of native controls (unlike WinForms which wraps Win32 in most cases), so why not emulate the Task Dialog, too?

One way to get around the fact that TaskDialogIndirect calls fail on XP and earlier is to catch the exception and fallback on something else automatically. This nets you the benefit of writing dialogs once for your application and trusting the wrapper classes to handle deciding whether or not to use the native or emulated Task Dialog depending on the current OS environment at runtime. One helpful fellow already thought of that and wrote a pretty good wrapper for WinForms years ago. I've used it several times, including improving it for our own internal purposes, and can attest to its awesomeness. It seamlessly falls back on a WinForms dialog that emulates the TaskDialog in the event that your app is running on XP. It's great but it was written for .NET 2.0 and WinForms.

Plenty of WPF Task Dialog implementations exist, but again like Hedley saw, you either have to pick a WPF custom dialog (trying to look sort of like a Task Dialog) or simply more wrapper classes for the native Win32 calls. At least as far as I could find, there does not appear to be a WPF solution that uses native when available and falls back on a WPF window in XP.

Well, now there is.

I spent a week or so on this first version and it works pretty well. I should note that I was going for seamlessness whenever possible. Many existing WPF Task Dialogs allow for XAML use as well as re-templating and customization, but there really isn't a way to do that and support the native calls, too. Instead, my WPF Task Dialog is merely used to mimic the look when native TaskDialogIndirect isn't available.

Hedley's code was under an open license, so I've pretty much ripped his work entirely as far as the native Win32 functionality goes. If I recall, though, he was basing his Win32 API code on someone else's work, too, and merely adding the emulator part. Well, in that case let me thank both of them for their hard work.

My work focused on two areas:

  • A WPF-based emulated Task Dialog fallback
  • Improving how you customize and show dialogs as well as how they report their results back to you

As such, my code suffers some of the same deficiencies as his. Task Dialogs normally support these features, but they are, so far, left out in this version of the wrapper:

  • Progress bars
  • HTML-like links in the content/footer text
  • Callbacks

One thing I do support that the WinForms one didn't is the Shield icon, supported natively by Task Dialogs. Icons in the emulated form will use the system icon of the current OS, except for the Shield icon (since it doesn't exist in XP). Curiously enough, adding this small improvement led me to a SO question that implies I'm not the first to tackle doing a WPF version of Hedley's work. Well, I couldn't find where that questioner had later released his work, so I'll try to make up my lack of innovation with being the first to get it out the door to the public, or something.

Anyway, I've got more details on how it was done and, most importantly, how to use it, over on CodeProject. Do check it out, download it, tell me what you think, and post any fixes, improvements, etc. there.

I will definitely see about fixing bugs and addressing some of the known limitations in the future, but I wanted to get this much out there now.

No comments:

Post a Comment