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

Shell Blog

Building Your First TaskDialog (Part 2)

Building Your First TaskDialog (Part 2)
Jeff Miller - September 20, 2006

Hello, and welcome back to the second part of this series on programming TaskDialogs.  If you haven't read part one of this series, you'll want to do that first, as it contains alot of information you'll need in order to follow the instructions in this document.

TaskDialog vs. TaskDialogIndirect

Perhaps some of you took my suggestion and looked into the documentation for TaskDialog.  If you did, you probably noticed that there are two different methods available for creating a TaskDialog, TaskDialog() and TaskDialogIndirect().  For now, I'm just going to discuss TaskDialog().  TaskDialogIndirect(), which is the more powerful of the two APIs will be covered next time.

The TaskDialog API

Here's how the TaskDialog method is defined (as found in commctrl.h, reformatted here to fit your screen):

WINCOMMCTRLAPI HRESULT WINAPI TaskDialog(
   
__in_opt HWND hwndParent, 
    __in_opt HINSTANCE hInstance, 
    __in_opt PCWSTR pszWindowTitle, 
    __in_opt PCWSTR pszMainInstruction, 
    __in_opt PCWSTR pszContent, 
    TASKDIALOG_COMMON_BUTTON_FLAGS dwCommonButtons, 
    __in_opt PCWSTR pszIcon, 
    __out_opt int *pnButton);

Let's compare and constrast those parameters to those of MessageBox (as found in winuser.h):

WINUSERAPI int WINAPI MessageBoxW(
    __in_opt HWND hWnd,
    __in_opt LPCWSTR lpText,
    __in_opt LPCWSTR lpCaption,
    __in UINT uType);

MessageBox returns an int, which is the identifier for the button which is pressed.  This value is returned in TaskDialog through the optional parameter (meaning that you can set it to NULL if you're not interested in finding out what button was pressed) "pnButton".  The TaskDialog API returns an HRESULT which will return rich error results if an error occurs, or S_OK if everything worked correctly.

MessageBox's first parameter is an hWnd which specifies what the owner window of this MessageBox will be. The TaskDialog parameter "hwndParent" is the same.

Skip to the third parameter, lpCaption (I never understood why this came third), which specifies what the caption of the MessageBox will read.  For a TaskDialog, this is the "pszWindowTitle" parameter.

The second parameter, lpText, defines the content string to be displayed.  TaskDialog splits this into two seperate parameters, "pszMainInstruction" and "pszContent".  The reason for the seperation is because TaskDialog follows the Windows Aero guidelines, which specify that you should have a "main instruction" which gives users a quick way to discover what is being asked or presented by this dialog, and a content section which provides supporting details for the main instruction.

The last parameter of MessageBox is uType which allows a set of flags to be passed in (MB_*) which specifies visual aspects of the MessageBox, such as an icon to display, what buttons should appear, etc. For TaskDialog, there is a stronger typed value here, a new enumeration which defines the buttons that will be displayed:

enum _TASKDIALOG_COMMON_BUTTON_FLAGS
{
    TDCBF_OK_BUTTON        = 0x0001, // selected control return value IDOK
    TDCBF_YES_BUTTON       = 0x0002, // selected control return value IDYES
    TDCBF_NO_BUTTON        = 0x0004, // selected control return value IDNO
    TDCBF_CANCEL_BUTTON    = 0x0008, // selected control return value IDCANCEL
    TDCBF_RETRY_BUTTON     = 0x0010, // selected control return value IDRETRY
    TDCBF_CLOSE_BUTTON     = 0x0020  // selected control return value IDCLOSE
};

The first of the two extra TaskDialog parameters, hInstance, makes it really easy to provide for localization of your TaskDialog.  I'll show you how in the next section. The second of the two extra TaskDialog parameters, pszIcon, allows you to specify your own icon to show in the TaskDialog, or alternately you can pass one of the predefined TaskDialog icon identifiers:

#define TD_WARNING_ICON         MAKEINTRESOURCEW(-1)
#define TD_ERROR_ICON           MAKEINTRESOURCEW(-2)
#define TD_INFORMATION_ICON     MAKEINTRESOURCEW(-3)
#define TD_SHIELD_ICON          MAKEINTRESOURCEW(-4)

Making localization easy

As mentioned in the previous section, the TaskDialog makes localization of dialogs much easier then with a MessageBox.  As you're probably aware, Windows allows you to read strings from the resource file so that all localizable resources are stored in one convienient place where they can be localized.  If you are using MessageBox, you likely have code that looks like the following:

WCHAR szCaption[128];
WCHAR szContent[128];
LoadString(hInstance, IDS_CAPTION, szCaption, ARRAYSIZE(szCaption));
LoadString(hInstance, IDS_CONTENT, szContent, ARRAYSIZE(szContent));
nButton = MessageBox(NULL, 
    szContent, szCaption,
    MB_OK);

A good developer will have a red flag raised and realize that 1) there's no error checking on the LoadString occurring, and 2) a fixed size buffer of 128 wide characters is provided.  What happens if the localized string ends up being longer than that?

Here's how the the same thing would look using TaskDialog:

hr = TaskDialog(NULL, hInstance, 
    MAKEINTRESOURCE(IDS_CAPTION), MAKEINTRESOURCE(IDS_CONTENT), NULL,
   
TDCBF_OK_BUTTON, NULL, &nButton);

Similarly, you can use the pszIcon parameter to have TaskDialog load an icon from a resource as shown here:

hr = TaskDialog(NULL, hInstance,
    MAKEINTRESOURCE(IDS_CAPTION), MAKEINTRESOURCE(IDS_CONTENT), NULL,
    TDCBF_OK_BUTTON, MAKEINTRESOURCE(IDI_MYICON), &nButton);

Where IDI_MYICON is an icon resource that you've added to your application's resources.

Easier to read, write and more robust!  How cool is that? 

A Real World Sample

Now let's put everything we've learned about the TaskDialog API together and try a real world sample.  If you're not sure how to build this, please re-read part one of this series, where I describe how to install and configure the Windows SDK.  All of the files you need to build this project are at the end of this part.  Copy them and build.  If all goes well, you'll be presented with a dialog when run that reads:

If you chose "Yes", you will get another TaskDialog that looks like this:

If you choose "No", then maybe this series isn't for you [:'(]

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

.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

Resource.h

#define IDS_CAPTION             10
#define IDS_CONTENT             11
#define IDS_MAININSTRUCTION     12

TaskDialog.rc

#include <resource.h>

CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "TaskDialog.exe.manifest"

STRINGTABLE
BEGIN
    IDS_CAPTION "Sample TaskDialog"
    IDS_MAININSTRUCTION "Do you like TaskDialogs?"
    IDS_CONTENT "A TaskDialog presents information in a clear and consistent way."
END

TaskDialog.c

#define UNICODE
#define _UNICODE
#include <windows.h>
#include <commctrl.h>
#include "resource.h"

int CALLBACK WinMain(
    __in HINSTANCE hInstance,
    __in_opt HINSTANCE hPrevInstance,
    __in_opt LPSTR lpCmdLine,
    __in int nShowCmd
    )
{
    HRESULT hr;
    int nButton;

    hr = TaskDialog(NULL, hInstance,
        MAKEINTRESOURCE(IDS_CAPTION), MAKEINTRESOURCE(IDS_MAININSTRUCTION), MAKEINTRESOURCE(IDS_CONTENT),
        TDCBF_YES_BUTTON | TDCBF_NO_BUTTON, NULL, &nButton);
    if (SUCCEEDED(hr))
    {
        if (nButton == IDYES)
        {
            // user chose "Yes" button...
            TaskDialog(NULL, NULL,
                L"Result", L"Here's your TaskDialog!", NULL,
                TDCBF_OK_BUTTON, TD_INFORMATION_ICON, NULL);
        }
        else if (nButton == IDNO)
        {
            // "No" button was chosen...
            MessageBox(NULL, 
                L"Here's your MessageBox!", L"Result", 
                MB_OK | MB_ICONINFORMATION);
        }
    }

    return 0;
}

Stay tuned for the next exciting chapter when we'll look at the TaskDialogIndirect function!

 

Published Tuesday, September 19, 2006 5:58 PM by uisamurai

Comments

 

Jason Haley said:

September 20, 2006 11:26 PM
 

pbradshaw said:

Please make sure this is supported natively in C# in the .Net 3.0 libraries!

September 21, 2006 4:45 PM
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.