Welcome to shell: revealed Sign in | Join | Help
in Search

Shell Blog

Building Your First TaskDialog (Part 3)

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:

  1. In the TASKDIALOGCONFIG struct, you need to set cButtons, pButtons and nDefaultButton instead of cRadioButtons, pRadioButtons and nDefaultRadioButtons, respectively.
  2. 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 barhyperlinks as well as different TaskDialog pages dynamically inside of the same frame.  I'll see you then!

 

Published Monday, October 16, 2006 2:51 PM by uisamurai

Comments

 

millenomi said:

Now we only need a managed API wrapper to this, possibly one that falls back to MessageBox or similar when TaskDialog is absent...

The problem about TD is that it won't be backported, and therefore will not be used for a very long time as software devs must support the huge installed base of XP. It'll take years.

October 17, 2006 4:01 AM
 

kiwiblue said:

It may be backported and distributed with IE7 through automatic updates, just like COMCTL32 variants with IE3/4/5.

October 17, 2006 5:31 AM
 

rev23dev said:

millenomi, in the Windows SDK there is something called VistaBridge.zip

You can get the managed TaskDialog stuff in there :)

October 17, 2006 10:56 AM
 

vinnyp said:

An alternative would be to use ShellMessageBox(), which is just a wrapper over TaskDialog() on Vista and MessageBox() on XP.

October 17, 2006 2:04 PM
 

davevr said:

Awesome post!   Hopefully everyone will use the new cool UI elements!

October 17, 2006 3:49 PM
 

Nidonocu said:

Is Vista Bridge in the SDK finished yet? Last time I tried to use the full TaskDialog Indirect, I found a lot of incomplete code and it just wasn't usable. Is this fixed in the RC1 SDK?

October 17, 2006 10:55 PM
 

pbradshaw said:

At this point, I only code in C#, so unless it's part of the .Net libraries, it doesn't exist to me.

October 18, 2006 2:19 AM
 

r_keith_hill said:

Ditto what pbradshaw says.  It's too bad managed supported is buried in some ZIP in the SDK.  That tells me that managed code access isn't a first class supported scenario.  If I'm wrong, let me know.  I guess I'm just disappointed in the whole downfall of WinFX as it was envisioned at PDC 2003.

October 18, 2006 3:47 PM
 

jtchris said:

Hey Nidonocu - I'm actually the dev on the Vista Bridge sample library, and the short answer to your "is it 'fixed' in RC1 is, sadly, no. There is still lots of unfinished business in the whole library, with LOTS of new stuff coming on line, including enhancements to the Common File Dialog (new File Open/Save dialogs in Vista), app restart and document recovery, OS version checking, Command Links in Windows Forms and WPF, friendly official OS stock icon access (ala foo.Icon = StockIcons.Printer;) and gobs more.

Very true that these aren't part of the official .NET libs, but that doesn't mean we aren't getting tons of feedback from all parties involved (I'm an internal dev, working on the Windows SDK dev team), so these aren't just gee-whiz wrappers. The Task Dialog, for example, has a whole pile of new semantics atop the native API to make it more powerful and .NET friendly. For example, all the "custom controls" you can host on a TD - custom buttons, marquee or progress bar, etc. are all (well, almost all) modelled by "pseudo controls" that are added to a controls collection on the TD. This makes it easier to customize the TD, but more importantly allows some cool scenarios in WPF's XAML, here you can easily define a complex TD declaratively, then just whip it up in your app whenever you need it. This technique is demonstrated in the Vista Bridge sample's demo app, so have a look!

October 26, 2006 12:41 PM
 

Roaster said:

Hi uisamurai,

reading your posting with great interest I spotted your last lines:

"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 couldn't find any sequel to this current posting here. I know it's quite a long time it had been written, however currently I'm dealing with the task dialog and I would appreciate to see the promised article ;-)

Thanks!

May 11, 2007 6:39 AM
 

Spooky said:

Seems this blog has been basically abandoned. I'd like to see the promised third part of this article.

January 31, 2008 7:54 AM
Anonymous comments are disabled

About uisamurai

I'm the development lead for the Windows Shell Visuals team. I've been at Microsoft since May 1995. I'm known for my coding, not my bio writing.
Powered by Community Server, by Telligent Systems © 2006 Microsoft Corporation. All rights reserved. Terms of Use | Trademarks | Privacy Statement.