Background

There are tons of other WPF/WinForms TaskDialog implementations out there, either those that wrap the Win32 APIs, or just providing a custom WPF dialog to use. A quick web search will find you plenty. What they don't give you is a unified way to use the native dialog that still works on older versions of Windows. I go into more detail on the rationale and history of all this in a blog post here.

Hedley's work is great and I've used it plenty of times, but I wanted something very similar that worked for WPF apps. I also thought I might take the time to try to improve on it, mostly thanks to the power of WPF and .NET 4.0.

Using the Code

For the most part, you'll only care about the TaskDialog class' static methods for invoking a dialog. I wanted it to sort of mimic the ease of use that the traditional MessageBox class has always given us. There are two other major classes that you'll use with the static methods:

TaskDialogOptions

TaskDialogResult

For cases where you need complete control over the customization of the task dialog, you'll have to create an options object and pass it in to the Show method. This means you can't exactly do a one-line method call anymore, but it does give you the most control in cases where that is important.

In simple cases, you'll get back an enum value, much like MessageBox returns (in fact, it is directly cast-able to DialogResult) that will let you know which button was clicked by the user. In the more complicated cases, especially when using an options object, you'll get back a full result object that includes everything you need to know about what the user did to your task dialog. You'll get back the index of any radio button selected, command link clicked, or common/custom button clicked.

And here's the corresponding dialog emulated on Windows 7 and Windows XP:

Accelerator keys can be specified (as per the Win32 API for TaskDialogs) by preceding the character with an ampersand (&), as seen in the above code. As you may already know, in WPF, accelerators are denoted by an underscore, but the emulated dialog accounts for this difference automatically.

Clickable hyperlinks can be specified by using the HTML equivalent anchor tag around some part of the text. Only the Content, ExpandedInfo, and FooterText properties support hyperlinks. You can use a callback handler to catch the clicks. Whatever value was specified as the href attribute can be used in the callback. An example can be found in the test project.

When using an options object, anything you leave blank or unspecified will simply not be shown, making it very flexible. You can also modify the static TaskDialogOptions.Default instance to set defaults and use it to create new options objects in the future. As an example, it might be useful to have the title caption always say your application's name by default unless specified. Since the options object is a struct value-type, you'll write TaskDialogOptions config = TaskDialogOptions.Default; instead of using the new operator as shown above.

Included in the source is a test application, much like Hedley's, that will let you try out several different kinds and see both the native (provided you are on Vista/7, of course) and emulated versions as well as what kind of information they return. I encourage you to look through the test project's code to see how it is showing task dialogs to get an idea of how to do the various kinds of dialogs.

More Examples

The following examples each show the code to invoke plus the corresponding dialog in native, emulated on Windows 7, and emulated on Windows XP, respectively. You can easily see that the emulated dialog is very close to the native, even on XP, and the icons are specific to the version of the OS running, too.

Showing a full error message:

TaskDialog.ShowMessage(this,
"Outlook",
"ActiveSync can't log on to Outlook",
"Make sure that Outlook is installed and functioning correctly.",
"You may need to restart your computer. You could have a conflict "
+ "due to two folders on this computer are name C:\\Program Files\\Microsoft "
+ "and C:\\Program Files\\Microsoft Office. If so, rename the "
+ "C:\\Program Files\\Microsoft folder so that it does not contain the word "
+ "\"Microsoft.\" If this folder contains subfolders, you may need "
+ "to reinstall programs in the renamed folder.\n\nFor more information "
+ "on renaming folders and installing programs, see Help for your "
+ "operating system.",
null,
null,
TaskDialogCommonButtons.Close,
VistaTaskDialogIcon.Error,
VistaTaskDialogIcon.None);

In the callback handler, you'll process timer events to update the progress bar's value or you can opt for a marquee (indeterminate) style. There's a pretty nice example implementation in the test project I suggest you take a look at. Callbacks are one of the more tricky things to handle but they can be very powerful.

Known Limitations/Bugs

Currently, though these features work in the native TaskDialog, they are not (yet) exposed by this wrapper:

Window position placement

Right-to-left layouts

Auto and custom window sizing

On Windows XP, the emulated dialog still shows an icon sometimes (native TaskDialogs and emulated dialogs on Vista/7 never show an icon)

Also, the emulated form could probably stand to have smarter auto-sizing logic. The native dialog seems to have this, but the exact rules for it are not exactly documented or obvious. I also didn't bother to try to implement the fancy fading and resizing animations that the native dialog uses when toggling the show/hide details button. It's not obvious from the screenshots, but the emulated command link is very close to the native and does have all the animations, too.

Leave any bugs, corrections, suggestions, or questions in the comments below. I generally answer them fairly promptly.

Share

About the Author

I'm a C# .NET developer for both Windows and Web applications since 2007, with a background in C/C++. Due to my job responsibilities currently, I've worn many hats. T-SQL in SQL Server, WinForms, WPF, WCF, ASP.NET, JavaScript, CSS, and more.

I was using WinApiCodePack for both CommonFileDialog and TaskDialog, but in several cases the App crashes due compatibility issues with SO versions.

I think is great this wrapper, really it was easy to rewrite code. I perform some test and compatibility issues have been solved. Unfortunately TaskDialog wasn't only one dialog I (and surely other devs) used. WinApiCodePack and the wrapper share (and crashes!!) with some classes and namespaces.

At this moment I think this project have been stopped. Perhaps, the suggestion is add CommonFileDialog support !!

If using this window in a winform, you cannot set this window (TaskdDialogOptions).owner to a winform because TaskdDialogOptions is a WPF window and not a winform.

I have tried using some System.Windows.Interop.WindowInteropHelper code to set the owner to a handle of the winform, but the modal WPF dialog that is created as TaskdDialogOptions is not a window, it is a TaskdDialogOptions object and thus will not cooperate with the System.Windows.Interop.WindowInteropHelper.

Any ideas on how to set the owner of your TaskdDialogOptions object to a winform? So the dialog will stay within the bounds of its owner?

Using radio options and custom buttons together, I get strange results:

• RadioButtonResult is always the index of the selected radiobox, never null.
• CustomButtonResult is always null, no matter which button I click.
• Simple Result is 500 for the first button, 501 for the second button and so on.

var result = TaskDialog.Show(
owner: View,
title: "MyApp",
mainInstruction: "Would you like to save the changes to the loaded file?",
content: "There are unsaved changes to the loaded files. " +
"If you load new files, you must either save or discard those changes.",
customButtons: newstring[] { "&Save", "Do&n't save", "&Cancel" });

I'm on Windows 7 SP1 x64, in a VS2010 .NET 4 WPF project, and I always seem to get the emulated dialogs. I've checked the ForceEmulationMode property, and it's false. Switching it to true doesn't change anything. What's wrong here? I just copied your binaries, didn't build it myself.

Then there's a focus issue in the emulated dialog. In a dialog with just 3 custom buttons and no other controls, pressing the Tab key cycles the three buttons but additionally the panel they're in. The entire panel gets that dashed (not dotted like in native windows) border outline. Something should get a Focusable=false I think.

But then it's a great solution that I could use very well right now. "OK, Cancel" dialogs with additional explanations of the buttons' meaning are ugly and even Microsoft has found that out and recommends not using them anymore...

Update: I've built a Debug version of the code, to find out what was happening in there. It threw an exception because it couldn't find the entry point TaskDialogIndirect in comctl32.dll. A web search brought me awful things. But I noticed that it only didn't work in the debugger (F5). When running without the debugger (Ctrl+F5), I got the native dialog. Quitting Visual Studio, a window popped up telling me that Visual Studio might need Administrator privileges to work correctly. I just discarded that stupid thing... And the strange thing now is - no matter what DLL I use now (yours, my Debug or my Release), it always works fine. Can't reproduce the bug. The admin popup also didn't come back. Is that a first-time issue? My computer needs some time to get used to new software?

I activated the callback and using it to display a count down like "In 5 seconds the second button will be clicked" in the footer.
It's working well but if the callback is enabled, all button click events (invoked by the user) will be redirected to the callback and nothing happens.
If I catch the button click event (by checking the Notification prop), any ClickButton(int) call will be redirected again...

Is there an solution?

Edit: I solved this problem by returning "false" if the button clicked notification is sent.
I suggest to update the xml documentation for the callback using this.
In addition, you should extend the ClickButton(int) method to distinguish between common button clicks and command button clicks. For the programmer it is not obvious that the command button ids are offsetted by 2000 and the "wrong" button is "clicked".

Returning false by default from your callback handler is recommended to ensure the dialog maintains default behavior. I've added some more notes about return values into the XML for the TaskDialogCallback delegate. Summaries for the various callback args Notification enum values (VistaTaskDialogNotification) already specify return value behavior, if any.

I've got a new version (1.7) coming soon that will include a few new features and improvements, including more convenient methods for simulating button clicks in callbacks. I agree the buttonId stuff is kind of esoteric, so as an alternative there will be index based ways.

Looking at the Microsoft documentation on the Win32 TASKDIALOGCONFIG structure, it specifically expects a string for the expanded area (well, optionally a resource id but this is just so it can do localization at runtime).

So, nope! If the native can't support it then I can't justify adding it to the emulated form.

My guess is that Windows/Microsoft is using a custom form that looks just like the standard Task Dialog. Or they are using some undocumented or unexposed method of showing it. Either way it doesn't really matter.

I suggest you do the same as them and just use a custom form yourself. You can just copy the XAML from mine if you really want it to look nearly identical. (Sorry the XAML is such a mess! I wrote it awhile back now and whenever I look at it now I wince; but hey, it works! I'm afraid to touch it now!)

AssemblyInfo version has been changed for awhile now (1.5) to match the article, but only on the GitHub repo for the code. Current build seems stable so far so I'll try to get this article updated by this weekend so the downloads are more current.

It's a bit confusing, I apologize. Mostly this is due to the API being the way that it is. Although I suppose I could have tried to expose it in a more friendly way. I mostly just wanted to get it implemented/supported at all, since it wasn't to begin with.

Before showing, set ShowMarqueeProgressBar to true. You don't need to set ShowProgressBar to true, but it shouldn't hurt anything if you do. Also, you'll want to set a callback and, typically, set EnableCallbackTimer to true so that you get regular timer events and you can control the progress bar based on some other process you're doing. In my test app example it is a file download. I could have shown a marquee while connecting and determining file size, as during that I have no way of showing determinate progress.

Anyway, as a minimal example, you can handle the Timer notification and do dialog.SetProgressBarMarquee(true, 0); and it should work. However, you should only do this ONCE! Otherwise, it'll sit there stuttering on the initial animation, because every 100ms the timer fires and resets the marquee. Instead, have some kind of boolean you can check so that you only call it the first time the timer fires.

Realistically, you'd have special logic to check various conditions on each timer fire to know when to set or unset the marquee animation. You can call dialog.SetMarqueeProgressBar(false); followed by calls to the various other progress methods like range/position to have it return to the normal determinate mode.

Hello!
As in your example for the progress bar, I set the options so that a timer callback is called:
config.ShowProgressBar = true;
config.EnableCallbackTimer = true;
config.Callback = taskDialog_Callback2;

From within the callback, I now want to close the dialog. The use-case is, the progress bar comes to the end (100%) and I wish to have the modal dialog return as if a user had clicked it away (weekend regression tests with no human present!).

Is this possible? The callback gets a parameter of type IActiveTaskDialog, which sports no method to close the dialog. There are two implemenations of IActiveTaskDialog: VistaActiveTaskDialog & TaskDialogViewModel. VistaActiveTaskDialog has a public but non-interface method called "ClickButton()" which does the job. TaskDialogViewModel has no such method. To use "ClickButton()" In any case I would have to cast to the specific implementation which for obvious reasons does not always work and can crash.

I'm constrained by what the API exposes, in this case only actions from TASKDIALOG_MESSAGES (from CommCtrl.h in the Windows SDK). You're right about the ClickButton action, as it would let you simulate a click on, say, the Close button or whatever. It's probably the best we can hope for, since the native Task Dialog does not expose an explicit CloseDialog method or action that I know of (could be wrong!).

You're also right about the IActiveTaskDialog bit. You might notice I left myself a note at the top of the interface code:

// TODO Support more of the methods exposed by VistaActiveTaskDialog class

ClickButton is one of those. The native dialog (VistaActiveTaskDialog) has it, but the emulated one (TaskDialogViewModel) doesn't yet. I suppose it is just a matter of making sure I can support it in the VM and then I can add it to the interface.

There is an issue for this on the GitHub project. Feel free to implement it yourself and send a pull request if you like, otherwise it'll be one of those "when I get around to it" kind of things.

I just started using this. So far, it's pretty easy to use. One thing I noticed...

Since a number of items have their colors set explicitly and some don't, I had some problems with styling due to having a default style in my WPF application. The buttons, checkbox, radio buttons, and text in the lower portion of the dialog had color issues because they inherited the style I had defined in my application. I resolved them by explicitly setting their foreground color to the system color or by setting the BasedOn attribute to null (like the checkbox fix below).

My intention was always to focus on the Win32 wrapping and emulator fallback. Some have criticized my lack of exposing templates or other WPF-friendly extension points. But again, it was never meant to be that really. It didn't make sense to expose those WPF things since the native dialog couldn't possibly support it.

I will definitely look into fixing the style inheritance, though. If only because it will mean the emulated dialog stays even truer to the native dialog (since obviously the native dialog won't inherit any of your custom styles).