Click here to Skip to main content
Click here to Skip to main content

WPF TaskDialog Wrapper and Emulator

By , 18 Oct 2012
 

The Short Version

Really quickly, here's what you need to know: 

  • Visual Studio 2010 solution in .NET 4.0 for WPF applications
  • Uses native TaskDialogIndirect calls when available (Vista/7)
  • Uses an emulated WPF version when not (XP)
  • Provides seamless fallback, lots of customizability, and detailed return results
  • Completely free to use or modify
  • Lots of improvements in v1.5+ — see below!

Introduction

I've been a big fan (and user) of Hedley Muscroft's WinForms emulator and wrapper for TaskDialogs. In that same spirit, I've taken part of his hard work (and, by succession, KevinGre's) and done something very similar, but this time for WPF (and .NET 4.0).

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.

Here's some code to show a dialog:

TaskDialogOptions config = new TaskDialogOptions();

config.Owner = this;
config.Title = "Task Dialog Title";
config.MainInstruction = "The main instruction text for the TaskDialog goes here";
config.Content = "The content text for the task dialog is shown " + 
                 "here and the text will automatically wrap as needed.";
config.ExpandedInfo = "Any expanded content text for the " + 
                      "task dialog is shown here and the text " + 
                      "will automatically wrap as needed.";
config.VerificationText = "Don't show me this message again";
config.CustomButtons = new string[] { "&Save", "Do&n't save", "&Cancel" };
config.MainIcon = VistaTaskDialogIcon.Shield;
config.FooterText = "Optional footer text with an icon can be included.";
config.FooterIcon = VistaTaskDialogIcon.Warning;

TaskDialogResult res = TaskDialog.Show(config);

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

Emulated Task Dialog in Windows 7

Emulated Task Dialog in 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.

Test Application

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

Message box

Message box emulated on Windows 7

Message box emulated on Windows XP

Showing radio button options:

TaskDialogOptions config = new TaskDialogOptions();

config.Owner = this;
config.Title = "RadioBox Title";
config.MainInstruction = "The main instruction text for the TaskDialog goes here.";
config.Content = "The content text for the task dialog is shown here "
               + "and the text will automatically wrap as needed.";
config.ExpandedInfo = "Any expanded content text for the task dialog "

                    + "is shown here and the text will automatically wrap as needed.";
config.RadioButtons = new string[] {
    "Radio Option 1", "Radio Option 2", 
    "Radio Option 3", "Radio Option 4", "Radio Option 5" };
config.MainIcon = VistaTaskDialogIcon.Information;

TaskDialogResult res = TaskDialog.Show(config);

Radio button box

Radio button box emulated on Windows 7

Radio button box emulated on Windows XP

Showing command links:

TaskDialogOptions config = new TaskDialogOptions();

config.Owner = this;
config.Title = "RadioBox Title";
config.MainInstruction = "The main instruction text for the TaskDialog goes here.";
config.Content = "The content text for the task dialog is shown here "
               + "and the text will automatically wrap as needed.";
config.ExpandedInfo = "Any expanded content text for the task dialog "

                    + "is shown here and the text will automatically wrap as needed.";
config.CommandButtons = new string[] {
    "Command &Link 1", "Command Link 2\nLine 2\nLine 3", "Command Link 3" };
config.MainIcon = VistaTaskDialogIcon.Information;

TaskDialogResult res = TaskDialog.Show(config);

Command link box

Command link box emulated on Windows 7

Command link box emulated on Windows XP

Using custom icons

Add the icon to your project with a Build Action of Resource. I just added mine via the default Resources.resx file as an Image resource.

TaskDialogOptions config = new TaskDialogOptions();

config.Owner = this;
config.Title = "Windows Genuine Verification";
config.MainInstruction = "This copy of Windows is not genuine.";
config.Content = "You may be a victim of software counterfeiting.";
config.CommonButtons = TaskDialogCommonButtons.Close;
config.CustomMainIcon = System.Drawing.Icon.FromHandle
			(Properties.Resources.genuine_32.GetHicon());

TaskDialogResult res = TaskDialog.Show(config);

Don't forget to dispose of it when you're done! (example: config.CustomMainIcon.Dispose();)

Task dialog box with custom icon

Showing progress bars:

TaskDialogOptions config = new TaskDialogOptions();

config.Owner = this;
config.Title = "Downloading File...";
config.MainInstruction = 
"Your file 'en_visual_studio_2010_ultimate_x86_dvd_509116.iso' is currently downloading";
config.Content = "Time elapsed: 00:00 | Download rate: 0 KB/s";
config.CustomButtons = new string[] { "&Reset Timer", "&Cancel" };
config.AllowDialogCancellation = true;
config.ShowProgressBar = true;
config.EnableCallbackTimer = true;
config.Callback = taskDialog_Callback2;

TaskDialogResult res = TaskDialog.Show(config);

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.

Progress bar box

Progress bar box emulated on Windows 7

Progress bar box emulated on Windows XP

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. 

History

  • 12/17/2010 - v1.0
    • Initial public release
  • 2/7/2011 - v1.0.1
    • Now uses DesignData feature of Visual Studio
    • Fixed a bug with default radio button selection (thanks pt1401)
    • Alt-F4 is now properly ignored by the emulated Task Dialog, when appropriate
  • 11/1/2011 - v1.1
    • Added an AllowDialogCancellation property to TaskDialogConfig
  • 11/2/2011 - v1.1.1
    • Fixed DefaultButtonIndex to work with the native Task Dialog (thanks Filip D'haene)
  • 12/21/2011 - v1.5
    • Added support for callbacks, progress bars, hyperlinks, and custom main/footer icons
    • Added support for several more customization options: expand footer (default is content), expanded by default, verification checkbox default state
    • While the core task dialog code is now mostly stable, these new features should be considered experimental for now
    • Fixed RadioButtonResult to not override the SimpleResult, so you can still see what button the user clicked (such as Cancel) as well as what radio item was selected (thanks in part to awrdsemb)
  • 9/20/2012 - v1.6
    • Added support for ClickButton during callbacks (see demo project in source for example usage)
    • Callback timer is now properly stopped when the emulated dialog is closed (thanks to niemyjski)
    • Changed emulated dialog to show OK button instead of Close when no buttons are specified
  • 10/18/2012 - v1.7 
    • Added additional button click methods by type and index for use in callbacks
    • Callback args now include the button index if the notification is related to a click 
    • Radio buttons can now be programmatically clicked as well
    • Added support for button enabling/disabling and setting elevation required in callbacks
    • Additional info about return values in TaskDialogCallback's XML documentation
    • Emulated form now correctly supports common and custom buttons when showing radio buttons, as well as a default OK when no others are specified 
    • Emulated form now correctly notifies callbacks when the dialog is closed via Esc, Alt+F4, etc.
    • Emulated form no longer has Retry/Cancel buttons mistakenly swapped 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Sean A. Hanley
Software Developer
United States United States
Member
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.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionUsing radio options and custom buttons [modified]memberYves Goergen22 Feb '13 - 12:25 
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.
 
Is there an interpretation mismatch in it somewhere?
 
I've extended the test application, button4, by this line:
 
config.CustomButtons = new string[] { "OK", "Cancel", "Third" };
 
Update: Just fixed this on GitHub, see my pull request there.

modified 22 Feb '13 - 19:02.

SuggestionUse named parameters for less code :-)memberYves Goergen22 Feb '13 - 11:36 
Wouldn't it be cool to have named parameters with the static Show method instead of creating an options instance first? Here's my try on this:
 
public static TaskDialogResult ShowTaskDialog(
	bool allowDialogCancellation = false,
	TaskDialogCallback callback = null,
	object callbackData = null,
	string[] commandButtons = null,
	TaskDialogCommonButtons commonButtons = TaskDialogCommonButtons.None,
	string content = null,
	string[] customButtons = null,
	System.Drawing.Icon customFooterIcon = null,
	System.Drawing.Icon customMainIcon = null,
	int? defaultButtonIndex = null,
	bool enableCallbackTimer = false,
	bool expandedByDefault = false,
	string expandedInfo = null,
	bool expandToFooter = false,
	VistaTaskDialogIcon footerIcon = VistaTaskDialogIcon.None,
	string footerText = null,
	VistaTaskDialogIcon mainIcon = VistaTaskDialogIcon.None,
	string mainInstruction = null,
	Window owner = null,
	string[] radioButtons = null,
	bool showMarqueeProgressBar = false,
	bool showProgressBar = false,
	string title = null,
	bool verificationByDefault = false,
	string verificationText = null)
{
	TaskDialogOptions options = new TaskDialogOptions();
	options.AllowDialogCancellation = allowDialogCancellation;
	options.Callback = callback;
	options.CallbackData = callbackData;
	options.CommandButtons = commandButtons;
	options.CommonButtons = commonButtons;
	options.Content = content;
	options.CustomButtons = customButtons;
	options.CustomFooterIcon = customFooterIcon;
	options.CustomMainIcon = customMainIcon;
	options.DefaultButtonIndex = defaultButtonIndex;
	options.EnableCallbackTimer = enableCallbackTimer;
	options.ExpandedByDefault = expandedByDefault;
	options.ExpandedInfo = expandedInfo;
	options.ExpandToFooter = expandToFooter;
	options.FooterIcon = footerIcon;
	options.FooterText = footerText;
	options.MainIcon = mainIcon;
	options.MainInstruction = mainInstruction;
	options.Owner = owner;
	options.RadioButtons = radioButtons;
	options.ShowMarqueeProgressBar = showMarqueeProgressBar;
	options.ShowProgressBar = showProgressBar;
	options.Title = title;
	options.VerificationByDefault = verificationByDefault;
	options.VerificationText = verificationText;
 
	return TaskDialog.Show(options);
}
 
This would be an example invokation:
 
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: new string[] { "&Save", "Do&n't save", "&Cancel" });

BugAlways getting emulated dialog; Focus issues [modified]memberYves Goergen22 Feb '13 - 10:55 
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? Wink | ;-)

modified 22 Feb '13 - 17:12.

SuggestionLocalizationmemberChrDressler25 Sep '12 - 9:32 
Hi,
 
great tool!
It wold be nice, if you support localization for the Button-Captions in Emulation mode in the next release. E.g. via Description-attribute for enums.
 
-christoph
GeneralRe: LocalizationmemberSean A. Hanley30 Sep '12 - 10:57 
This is definitely something I'd like to implement. In the mean time, at least, you can always opt for using Custom buttons exclusively and control localization yourself.
 
I will enter this suggestion on the Github project, though.
 
Thanks!
QuestionCommand button click not working with enabled callback [modified]memberarchitecton21 Sep '12 - 2:27 
Hi,
 
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".

modified 21 Sep '12 - 8:52.

AnswerRe: Command button click not working with enabled callbackmemberSean A. Hanley21 Sep '12 - 7:26 
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.
 
Thanks for the feedback!
QuestionSupport for Scrollable Region At BottommemberjoelGordon5 Sep '12 - 19:26 
Firstly, you've done an awesome job Sean - thanks very much.
 
My question is is there support for a scrollable region at the bottom of the dialog as per this screen snapshot taken from Win 7 ?
 
https://www.dropbox.com/s/k3xb73a1p4uqcss/Error%20Dialog%20with%20Scrollable%20Region.jpg[^]
 
If not is there any plans to add it (was really after this so I could display an abitrarily large amount of text - I'm not concerned with support for hyperlinks as shown in the screen snapshot) ?
 
Thanks for your time,
Joel Gordon.
AnswerRe: Support for Scrollable Region At BottommemberSean A. Hanley6 Sep '12 - 12:16 
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!)
GeneralRe: Support for Scrollable Region At BottommemberjoelGordon19 Sep '12 - 12:41 
Thanks for the prompt reply - have done as you suggest (XAML wasn't that bad Smile | :) .
 
One minor suggestion is that you update the AssemblyInfo with the current version number (which I believe is 1.5 - from the History section in your article).
 
Cheers,
Joel

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 18 Oct 2012
Article Copyright 2010 by Sean A. Hanley
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid