Building Your First TaskDialog (Part 3)
Jeff Miller - October 16, 2006
UPDATE: October 17, 2006 -- made a bunch of changes based on guidelines feedback from Everett McKay. Also made source code downloadable by request of Ales Holecek. Thanks to both of them!
Whoa, it's been almost a month since the last installment of this series. Apologies for the wait for this part, but I've been busy surfing the web and listening to mp3s shipping Vista. In this part, we'll start looking at the TaskDialogIndirect() API, which provides a way to create a TaskDialog which is completely customizable by you!
The TaskDialogIndirect API
Here's how the TaskDialogIndirect method is defined (as found in commctrl.h, reformatted here to fit your screen):
WINCOMMCTRLAPI HRESULT WINAPI TaskDialogIndirect(
const TASKDIALOGCONFIG *pTaskConfig,
__out_opt int *pnButton,
__out_opt int *pnRadioButton,
__out_opt BOOL *pfVerificationFlagChecked
);
Here's all you need to know about the parameters for this function:
- pTaskConfig is an initialized TASKDIALOGCONFIG structure which describes how and what to display in this TaskDialog. As with many other comctl functions, it's important that you initialize the structure before using it. I recommend a pattern such as:
TASKDIALOGCONFIG tc = { 0 };
tc.cbSize = sizeof(tc);
// additional initialization here...
hr = TaskDialogIndirect(&tc, NULL, NULL, NULL);
- pnButton is an optional out parameter (it can be set to NULL if you're not interested in the result) which will indicate which button was pressed.
- pnRadioButton is only applicable when your TaskDialog contains radio buttons -- see the TASKDIALOGCONFIG.cRadioButtons parameter. It is an optional out parameter which will indicate which radio button was selected when the TaskDialog was dismissed.
- pfVerificationFlagChecked is related to the TASKDIALOGCONFIG.pszVerificationText parameter (see also the TDF_VERIFICATION_FLAG_CHECKED flag and the TDM_CLICK_VERIFICATION message). If the verification checkbox is showing, then this optional out parameter will indicate the state of the verification checkbox when the TaskDialog was dismissed.
It's always easier to show an example, so let's go ahead and modify our sample to demonstrate these parameters.
Our First TaskDialogIndirect Sample - The Source Code
Download the source code for this sample TaskDialog sample (part3, exercise 1).
I'll show the updated source files first, then will describe the changes in detail afterwards.
resource.h
#define IDS_WINDOWTITLE 10
#define IDS_CONTENT 11
#define IDS_MAININSTRUCTION 12
#define IDS_VERIFICATIONTEXT 13
#define IDS_FOOTER 15
#define IDS_RB_GOOD 16
#define IDS_RB_OK 17
#define IDS_RB_BAD 18
#define IDS_CB_SAVE 19
TaskDialog.c
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <commctrl.h>
#include <strsafe.h>
#include "resource.h"
int CALLBACK WinMain(
__in HINSTANCE hInstance,
__in_opt HINSTANCE hPrevInstance,
__in_opt LPSTR lpCmdLine,
__in int nShowCmd
)
{
HRESULT hr;
TASKDIALOGCONFIG tc = { 0 };
int nButton;
int nRadioButton;
BOOL fVerificationFlagChecked;
#define CB_SAVE 200
const TASKDIALOG_BUTTON cb[] =
{
{ CB_SAVE, MAKEINTRESOURCE(IDS_CB_SAVE) },
};
#define RB_GOOD 102
#define RB_OK 101
#define RB_BAD 100
const TASKDIALOG_BUTTON rb[] =
{
{ RB_GOOD, MAKEINTRESOURCE(IDS_RB_GOOD) },
{ RB_OK, MAKEINTRESOURCE(IDS_RB_OK) },
{ RB_BAD, MAKEINTRESOURCE(IDS_RB_BAD) },
};
tc.cbSize = sizeof(tc);
// Note: GetDesktopWindow() is used here only for this sample
// You should use something more reasonable, such as the HWND of your application
tc.hwndParent = GetDesktopWindow();
tc.hInstance = hInstance;
tc.dwFlags = TDF_USE_HICON_MAIN | TDF_USE_HICON_FOOTER | TDF_ALLOW_DIALOG_CANCELLATION | TDF_EXPAND_FOOTER_AREA;
tc.dwCommonButtons = TDCBF_CANCEL_BUTTON;
// TaskDialog icons
LoadIconWithScaleDown(NULL, IDI_APPLICATION,
GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON),
&tc.hMainIcon);
LoadIconWithScaleDown(NULL, IDI_INFORMATION,
GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
&tc.hFooterIcon);
// TaskDialog strings
tc.pszWindowTitle = MAKEINTRESOURCE(IDS_WINDOWTITLE);
tc.pszMainInstruction = MAKEINTRESOURCE(IDS_MAININSTRUCTION);
tc.pszContent = MAKEINTRESOURCE(IDS_CONTENT);
tc.pszVerificationText = MAKEINTRESOURCE(IDS_VERIFICATIONTEXT);
tc.pszFooter = MAKEINTRESOURCE(IDS_FOOTER);
// TaskDialog buttons
tc.cButtons = ARRAYSIZE(cb);
tc.pButtons = cb;
// TaskDialog radio buttons
tc.cRadioButtons = ARRAYSIZE(rb);
tc.pRadioButtons = rb;
tc.nDefaultRadioButton = RB_OK;
hr = TaskDialogIndirect(&tc, &nButton, &nRadioButton, &fVerificationFlagChecked);
if (SUCCEEDED(hr))
{
if (nButton == CB_SAVE)
{
switch (nRadioButton)
{
case RB_GOOD:
TaskDialog(NULL, NULL,
L"TaskDialog Result",
L"You like TaskDialogs alot", L"I'm glad you like TaskDialogs!",
TDCBF_OK_BUTTON, NULL, NULL);
break;
case RB_OK:
TaskDialog(NULL, NULL,
L"TaskDialog Result",
L"You like TaskDialogs a little bit", L"Maybe we'll do better next time.",
TDCBF_OK_BUTTON, NULL, NULL);
break;
case RB_BAD:
TaskDialog(NULL, NULL,
L"TaskDialog Result",
L"You don't like TaskDialogs at all", L"Back to MessageBox you go!",
TDCBF_OK_BUTTON, NULL, NULL);
break;
}
if (fVerificationFlagChecked)
{
// the user checked the verification flag...
// do any additional work here
}
}
else
{
// user cancelled out of the dialog
}
}
else
{
// some error occurred...check hr to see what it is
}
return 0;
}
TaskDialog.exe.manifest
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="X86"
name="Microsoft.TestApps.TaskDialog"
type="win32"
/>
<description>TaskDialog Test Application</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="X86"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>
TaskDialog.mak
!include <win32.mak>
all: TaskDialog.exe
clean:
del TaskDialog.res
del TaskDialog.obj
del TaskDialog.pdb
del TaskDialog.exe
.c.obj:
$(cc) $(cdebug) $(cflags) $(cvars) $*.c
.rc.res:
$(rc) $(rcflags) $(rcvars) $*.rc
TaskDialog.exe: TaskDialog.obj TaskDialog.res
$(link) $(ldebug) $(guilflags) -out:TaskDialog.exe TaskDialog.obj TaskDialog.res $(guilibs) comctl32.lib
TaskDialog.rc
#include <resource.h>
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "TaskDialog.exe.manifest"
STRINGTABLE
BEGIN
IDS_WINDOWTITLE "Sample TaskDialog"
IDS_MAININSTRUCTION "How much do you like TaskDialogs?"
IDS_CONTENT "A TaskDialog presents information in a clear and consistent way."
IDS_VERIFICATIONTEXT "&Don't show this message again"
IDS_FOOTER "The answer provided here will help determine your Aero worthiness."
IDS_RB_GOOD "L&ots"
IDS_RB_OK "A &little"
IDS_RB_BAD "No&t at all"
IDS_CB_SAVE "&Save my response"
END
Building the Project
Here's the files you should have in your project directory:
resource.h
TaskDialog.c
TaskDialog.exe.manifest
TaskDialog.mak
TaskDialog.rc
Inside of the Windows SDK build environment (see part 1 of this series for more information), do a "nmake /f TaskDialog.mak clean" followed by a "nmake /f TaskDialog.mak" to build the project. If all goes well, you'll end up with TaskDialog.exe, which is the TaskDialogIndirect test executable.
Our First TaskDialogIndirect Sample - The Analysis
It's important to note that the TaskDialog that I created here is mainly to demonstrate a bunch of features in the TaskDialogIndirect API. In general, your dialogs should not come out looking this "busy". If they do, you might need to refactor the items which you are displaying on your dialog. A picture is worth 1000 words, so in order to save myself some typing, I'll go ahead and show what the output of the project looks like:

As you can tell, with a minimal amount of code, we've created a clean, consistent dialog with quite a bit of functionality. The items that we have here include:
Custom icons in the header and footer.
Main instruction, content text and footer text, all formatted to the Aero standard.
Radio buttons
A push button consisting of custom text ("Save my response")
A standard push button ("Cancel")
A "verification" checkbox
Not bad for about 20 lines of code. Try creating that dialog using the "old" Win32 API calls, and you'll see that the TaskDialogIndirect API provides much value to the developer.
Radio Buttons
Radio buttons are enabled if TASKDIALOGCONFIG.cRadioButtons is greater than zero. If cRadioButtons is greater than zero, then pRadioButtons contains an array of TASKDIALOG_BUTTON items which describe the buttons that will be displayed. Lastly, nDefaultRadioButton optionally defines by ID which radio button should be selected by default. I find the following pattern useful for setting up radio buttons:
#define RB_ONE 101
#define RB_TWO 102
#define RB_THREE 103
TASKDIALOG_BUTTON rb[] =
{
{ RB_ONE, MAKEINTRESOURCE(IDS_RB_ONE) },
{ RB_TWO, MAKEINTRESOURCE(IDS_RB_TWO) },
{ RB_THREE, MAKEINTRESOURCE(IDS_RB_THREE) },
};
TASKDIALOGCONFIG tdc = { 0 };
tdc.cbSize = sizeof(tdc);
// ...more initialization here...
// set up the radio buttons, with "RB_TWO" as the default
tdc.cRadioButtons = ARRAYSIZE(rb);
tdc.pRadioButtons = rb;
tdc.nDefaultRadioButton = RB_TWO;
Where IDS_RB_ONE, IDS_RB_TWO and IDS_RB_THREE are three strings defined in the application's resource file with the text which is to be displayed for each radio button.
After calling TaskDialogIndirect(), the pnRadioButton field will contain the ID of the selected radio button, for example, RB_TWO.
Custom Push Buttons
Custom push buttons are initialized in the exact way that radio buttons are -- see the previous section for information. The only differences you need to be aware of are:
- In the TASKDIALOGCONFIG struct, you need to set cButtons, pButtons and nDefaultButton instead of cRadioButtons, pRadioButtons and nDefaultRadioButtons, respectively.
- The push button chosen is returned through the pnButton parameter of the TaskDialogIndirect call.
Other than those, custom push buttons work and act like radio buttons when using TaskDialogIndirect.
Verification Checkbox
One very common request for MessageBox was the ability to add a check box to indicate some toggle specific to the dialog being presented. Most commonly this was a "Do not show me this dialog box again" related checkbox. TaskDialogIndirect provides this functionality and makes it extremely easy to use. Simply set the pszVerificationText field of the TASKDIALOGCONFIG structure, and your TaskDialog will include a checkbox, as shown in the samples above. If you would like the checkbox to be checked by default, then simply add the TDF_VERIFICATION_FLAG_CHECKED flag to the dwFlags member. The state of the checkbox will be returned through the pfVerificationFlagChecked parameter of the TaskDialogIndirect API.
One design guideline you should consider when using the verification checkbox is to avoid combining the More/Fewer (expando button, as described below) with the "Don't show this <item> again" checkbox. This combination ends up having an awkward appearance.
Expando Button Sample - The Source Code
Download the source code for this sample TaskDialog sample (part 3, ex 2).
Here's the changes that we need from the previous sample:
resource.h
add:
#define IDS_EXPANDEDINFORMATION 14
TaskDialog.c
replace the following line:
tc.pszVerificationText = MAKEINTRESOURCE(IDS_VERIFICATIONTEXT);
with:
tc.pszExpandedInformation = MAKEINTRESOURCE(IDS_EXPANDEDINFORMATION);
TaskDialog.rc
add the following line:
IDS_EXPANDEDINFORMATION "By giving a low rating to the above question, you may be indicating that you are a troglodyte."
Expando Button Sample - The Analysis
The Expando control of a TaskDialog can provide the user with more information, generally of a detailed nature, which most users might not be interested in. In our example, if the user presses the "See details" button, the TaskDialog is expanded, a details section is provided at the bottom of the dialog and the button text changes to read "Hide details".


In order for the expando button to show, the pszExpandedInformation field of the TASKDIALOGCONFIG structure must be filled out. When filled out, the expando button will appear with default text of "See details" / "Hide details" (or the localized equivalent), and the TaskDialog will be collapsed by default. If you wish the TaskDialog to be expanded by default, set the pszExpandedInformation field, and include the TDF_EXPANDED_BY_DEFAULT flag in the dwFlags field of the TASKDIALOGCONFIG structure. You can specify what text you wish to be used in place of "See details" / "Hide details" by filling in the pszCollapsedControlText and pszExpandedControlText fields, respectively.
One final customization of the expando comes from the TDF_EXPAND_FOOTER_AREA flag. This flag determines where the expanded information will be presented. If this flag is set, then you will get a new area at the bottom of the TaskDialog which displays the expanded information text, as shown above. However, if this flag is not present, the expanded information text will display as shown below:

Note that the expanded information (the "By giving a low rating..." text) is now presented above the radio buttons. Pressing "Hide details" will cause that additional text to disappear.
Command Links Sample - The Source Code
Download the source code for this sample TaskDialog sample (part 3, ex 3).
Here's the changes that are required from the previous example:
TaskDialog.c
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <commctrl.h>
#include <strsafe.h>
#include "resource.h"
int CALLBACK WinMain(
__in HINSTANCE hInstance,
__in_opt HINSTANCE hPrevInstance,
__in_opt LPSTR lpCmdLine,
__in int nShowCmd
)
{
HRESULT hr;
TASKDIALOGCONFIG tc = { 0 };
int nButton;
#define CL_GOOD 102
#define CL_OK 101
#define CL_BAD 100
const TASKDIALOG_BUTTON cl[] =
{
{ CL_GOOD, MAKEINTRESOURCE(IDS_RB_GOOD) },
{ CL_OK, MAKEINTRESOURCE(IDS_RB_OK) },
{ CL_BAD, MAKEINTRESOURCE(IDS_RB_BAD) },
};
tc.cbSize = sizeof(tc);
// Note: GetDesktopWindow() is used here only for this sample
// You should use something more reasonable, such as the HWND of your application
tc.hwndParent = GetDesktopWindow();
tc.hInstance = hInstance;
tc.dwFlags = TDF_USE_HICON_MAIN | TDF_USE_HICON_FOOTER | TDF_ALLOW_DIALOG_CANCELLATION | TDF_EXPAND_FOOTER_AREA | TDF_USE_COMMAND_LINKS;
// TaskDialog icons
LoadIconWithScaleDown(NULL, IDI_APPLICATION,
GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON),
&tc.hMainIcon);
LoadIconWithScaleDown(NULL, IDI_INFORMATION,
GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
&tc.hFooterIcon);
// TaskDialog strings
tc.pszWindowTitle = MAKEINTRESOURCE(IDS_WINDOWTITLE);
tc.pszMainInstruction = MAKEINTRESOURCE(IDS_MAININSTRUCTION);
tc.pszContent = MAKEINTRESOURCE(IDS_CONTENT);
tc.pszExpandedInformation = MAKEINTRESOURCE(IDS_EXPANDEDINFORMATION);
tc.pszFooter = MAKEINTRESOURCE(IDS_FOOTER);
// TaskDialog buttons
tc.cButtons = ARRAYSIZE(cl);
tc.pButtons = cl;
hr = TaskDialogIndirect(&tc, &nButton, NULL, NULL);
if (SUCCEEDED(hr))
{
switch (nButton)
{
case CL_GOOD:
TaskDialog(NULL, NULL,
L"TaskDialog Result",
L"You like TaskDialogs alot", L"I'm glad you like TaskDialogs!",
TDCBF_OK_BUTTON, NULL, NULL);
break;
case CL_OK:
TaskDialog(NULL, NULL,
L"TaskDialog Result",
L"You like TaskDialogs a little bit", L"Maybe we'll do better next time.",
TDCBF_OK_BUTTON, NULL, NULL);
break;
case CL_BAD:
TaskDialog(NULL, NULL,
L"TaskDialog Result",
L"You don't like TaskDialogs at all", L"Back to MessageBox you go!",
TDCBF_OK_BUTTON, NULL, NULL);
break;
default:
// user cancelled out of the dialog
break;
}
}
else
{
// some error occurred...check hr to see what it is
}
return 0;
}
TaskDialog.rc
Modify IDS_RB_GOOD, IDS_RB_OK, and IDS_RB_BAD as follows:
IDS_RB_GOOD "L&ots (Recommended)\nYou really like TaskDialogs."
IDS_RB_OK "A &little\nYou have some interest in TaskDialogs."
IDS_RB_BAD "No&t at all\nTaskDialogs aren't interesting to you at all."
Command Links Sample - The Analysis
Note that in the first sample provided in this part of the series, ignoring the validation checkbox, the user really only has one choice -- the response to the question. Instead of using radio buttons, and forcing an extra click (the "Save My Response" push button) a better design might be to use command links, which will allow the question to be answered with a single push of a button. Here's what the updated dialog would look like:

Looks alot cleaner, no? Basically, the changes that were required were to add TDF_USE_COMMAND_LINKS to dwFlags. By specifying this flag, any custom buttons (in the pButtons member) will be displayed as a command link, and the button that is pressed will be passed back to the caller through the pnButton parameter of TaskDialogIndirect. Alternately, you can specify TDF_USE_COMMAND_LINKS_NO_ICON, which would give the same behavior, except that the command links would not contain those green "->" arrows.
The observant among you will also note that in the picture above, the Command Link button shown for "Lots" also has a supporting text (a "note"). In order to display that, you will need to modify the RC file to have a string with a new line, which seperates the command link text from the note text. The format for command link buttons is "<link text>\n<optional note text>".
Don't forget the guidelines!
Before creating TaskDialogs, I highly recommend that you take a look at the Windows UX Guide which is available online. In particular, the section on Dialog Boxes will provide you will much, if not all, of the guidelines you need to use the TaskDialog and TaskDialogIndirect APIs to create dialogs that meet the Vista guidelines.
Conclusion
I hope that I have shown you some of the power of the TaskDialogIndirect API. In the next part of this series, I will show some really unique features of the TaskDialogIndirect API -- being able to display a progress bar, hyperlinks as well as different TaskDialog pages dynamically inside of the same frame. I'll see you then!