Contents
Introduction
In this Vista Goodies article, I will cover the new TaskDialog()
API, which is designed to be a more user-friendly replacement for MessageBox()
. TaskDialog()
not only shows a nicer-looking UI, but it's also easier for the developer since it handles details like automatic loading of string resources.
This article is written for the RTM version of Vista, using Visual Studio 2005, WTL 7.5, and the Windows SDK. See the introduction in the first Vista Goodies article for more information on where you can download those components.
While it's always been easy to show simple messages using MessageBox()
, it can be cumbersome to use at time, especially if your app is written with internationalization in mind and your UI strings are in a resource DLL: MessageBox()
has no provision for loading string resources, unlike other APIs like DialogBox()
which can be passed a resource ID. There is also the drawback that a message box can show only predefined sets of buttons; if you wanted to have a Retry button but not an Ignore button, you'd be out of luck.
While these shortcomings were addressed by MessageBoxIndirect()
, that API still has the restriction on which buttons can be displayed. TaskDialog()
solves that problem and provides a much easier-to-use and more flexible API.
The TaskDialog API
The TaskDialog()
prototype is:
HRESULT TaskDialog ( HWND hWndParent, HINSTANCE hInstance,
PCWSTR pszWindowTitle, PCWSTR pszMainInstruction,
PCWSTR pszContent,
TASKDIALOG_COMMON_BUTTON_FLAGS dwCommonButtons,
PCWSTR pszIcon, int *pnButton );
The parameters are:
hWndParent
- The window that will be the task dialog's parent. As with
MessageBox()
, this window is disabled while the task dialog is displayed. Pass NULL
if you don't want the task dialog to have a parent (see this blog post by Raymond Chen on why passing GetDesktopWindow()
is wrong).
hInstance
- If any of the following parameters are the ID of a string or icon resource, set this parameter to the
HINSTANCE
of the module that contains the resources.
pszWindowTitle
- The string to show in the task dialog's caption. This can either be a zero-terminated Unicode string or a string resource ID. When passing a resource ID, use the
MAKEINTRESOURCE
macro on the ID. (If you are building for the ANSI character set, use the Unicode macro MAKEINTRESOURCEW
explicitly instead of MAKEINTRESOURCE.
)
pszMainInstruction
- The string to show at the top of the task dialog, beside the icon. This text is shown in a larger font and in a different color, making it appear like a heading in a document. As with
pszWindowTitle
, this parameter can be a C-style string or a string resource ID.
pszContent
- The string to show in the body of the task dialog. This parameter can also be a C-style string or a string resource ID.
dwCommonButtons
- A set of flags indicating which buttons to show in the dialog. To have multiple buttons, use the logical-or operator to combine the flags for the buttons you want. The possible flags are:
TDCBF_OK_BUTTON
, TDCBF_YES_BUTTON
, TDCBF_NO_BUTTON
, TDCBF_CANCEL_BUTTON
, TDCBF_RETRY_BUTTON
, TDCBF_CLOSE_BUTTON
.
There is no restriction on which buttons can appear together; you can set as many flags in this parameter as you wish. If you pass 0, the default behavior is to just have an OK button. Note that the dialog will not be closeable using the Esc or Alt+F4 keys unless TDCBF_CANCEL_BUTTON
is passed.
pszIcon
- The resource ID of an icon to show in the task dialog. As with the string parameters, you must use the
MAKEINTRESOURCE
or MAKEINTRESOURCEW
macro on the ID. You can also pass one of these predefined values to use a system icon: TD_ERROR_ICON
, TD_WARNING_ICON
, TD_INFORMATION_ICON
, TD_SHIELD_ICON
. TD_SHIELD_ICON
displays the UAC shield () and should only be used to indicate a situation where the user will need to elevate to perform an administrative task. You can also pass NULL
if you don't want any icon at all.
pnButton
- A pointer to an
int
that is set to one of the following values to indicate which button the user clicks: IDOK
, IDYES
, IDNO
, IDCANCEL
, IDRETRY
, IDCLOSE
. If you pass TDCBF_CANCEL_BUTTON
in dwCommonButtons
, then *pnButton
will be set to IDCANCEL
if the user presses Esc or Alt+F4. If TaskDialog()
fails, the value will be set to 0. You can pass NULL
for this parameter if you don't need to know which button was clicked.
The return value from TaskDialog()
is an HRESULT
that indicates whether the function succeeds. It can fail if it is unable to load a resource, or in low-memory conditions.
If pszWindowTitle
is NULL
, the executable's filename is used for the caption text. pszMainInstruction
or pszContent
may be NULL
if you don't want to have that piece of text in the dialog. Also, pszMainInstruction
and pszContent
can be multi-line strings; put a newline character ('\n'
) in the string to indicate a line break.
Note that the string parameters are always Unicode strings. As with many APIs added in XP and Vista, there is no version that accepts ANSI strings.
Examples using TaskDialog
Using TaskDialog with C-Style Strings
Let's start with an example that just uses hard-coded strings. The context in which we're showing this message is that our app has determined that an update is available, and we are asking the user if he wants to get the update now.
void CTheApp::OnUpdateAvailable()
{
HRESULT hr;
int nClickedButton;
LPCWSTR szTitle = L"Mike's AntiFluff Scanner",
szHeader = L"An update for Mike's AntiFluff Scanner is available",
szBodyText = L"Version 2007.1 of Mike's AntiFluff Scanner has been released." \
L"Do you want to download the update now?";
hr = TaskDialog ( m_hWnd, NULL,
szTitle, szHeader, szBodyText,
TDCBF_YES_BUTTON|TDCBF_NO_BUTTON,
NULL, &nClickedButton );
if ( SUCCEEDED(hr) && IDYES == nClickedButton )
{
}
}
Here's the task dialog, showing the strings and buttons that we passed in:
We can also add an information icon with the pszIcon
parameter:
hr = TaskDialog ( m_hWnd, NULL,
szTitle, szHeader, szBodyText,
TDCBF_YES_BUTTON|TDCBF_NO_BUTTON,
TD_INFORMATION_ICON, &nClickedButton );
Using TaskDialog with Resources
Now let's see how to use TaskDialog()
when the strings and icon are in a resource DLL. We will need the HINSTANCE
of the DLL that contains the resources. For this example, let's assume that the DLL has already been loaded, so all we need to do here is retrieve that handle. The way of doing this varies among class libraries; here are the functions you can call in ATL, WTL, and MFC:
- ATL in VC 6 and WTL using a global
CAppModule
variable: _Module.GetResourceInstance()
- ATL in VC 7 and later:
_AtlBaseModule.GetResourceInstance()
- MFC in VC 6:
AfxGetResourceHandle()
- MFC in VC 7 and later:
AfxGetResourceHandle()
and AfxFindResourceHandle()
First, let's replace the hard-coded strings with resource IDs:
HINSTANCE hinst = _Module.GetResourceInstance();
hr = TaskDialog ( m_hWnd, hinst,
MAKEINTRESOURCE(IDS_SAMPLEDLG_TITLE),
MAKEINTRESOURCE(IDS_SAMPLEDLG_HEADER),
MAKEINTRESOURCE(IDS_SAMPLEDLG_BODY),
TDCBF_YES_BUTTON|TDCBF_NO_BUTTON,
TD_INFORMATION_ICON, &nClickedButton );
And finally, let's add our own icon to the dialog:
HINSTANCE hinst = _Module.GetResourceInstance();
hr = TaskDialog ( m_hWnd, hinst,
MAKEINTRESOURCE(IDS_SAMPLEDLG_TITLE),
MAKEINTRESOURCE(IDS_SAMPLEDLG_HEADER),
MAKEINTRESOURCE(IDS_SAMPLEDLG_BODY),
TDCBF_YES_BUTTON|TDCBF_NO_BUTTON,
MAKEINTRESOURCE(IDI_TD_SAMPLE_ICON),
&nClickedButton );
Here's the final dialog with our custom icon:
That's all there is to it! While TaskDialog()
is a snap to use, the dialog is still not much more complex than a message box. In the next article, I'll cover TaskDialogIndirect()
, which has a lot more power and many more UI features.
Using the Sample App
The demo project for this article is a task dialog builder. You can enter the text you want to show in the caption, header, and body areas, along with the buttons and an optional icon:
The app then calls TaskDialog()
with those parameters, so you can see the dialog in action:
Further Reading
Copyright and License
This article is copyrighted material, ©2006 by Michael Dunn. I realize this isn't going to stop people from copying it all around the 'net, but I have to say it anyway. If you are interested in doing a translation of this article, please email me to let me know. I don't foresee denying anyone permission to do a translation, I would just like to be aware of the translation so I can post a link to it here.
The demo code that accompanies this article is released to the public domain. I release it this way so that the code can benefit everyone. (I don't make the article itself public domain because having the article available only on CodeProject helps both my own visibility and the CodeProject site.) If you use the demo code in your own application, an email letting me know would be appreciated (just to satisfy my curiosity about whether folks are benefiting from my code) but is not required. Attribution in your own source code is also appreciated but not required.
Revision History
- December 12, 2006: Article first published.
- December 26, 2006: No content changes, just updated series navigation links.
Series navigation: « Using the New Vista File Dialogs | Using TaskDialogIndirect to Build Dialogs that Get User Input »