The Wayback Machine - https://web.archive.org/web/20170112095118/http://www.codeguru.com/cpp/com-tech/shell/general/article.php/c13183/Task-Dialogs-in-Vista.htm

Task Dialogs in Vista

Introduction

How many times have you come across a situation where you have a message box pop up with some text in it and three buttons: Yes, No, and Cancel. Have you ever been stumped by the options, trying to figure out what the difference was with selecting No versus selecting Cancel? Well, I have had to scratch my head on numerous occasions where a MessageBox simply wasn't conveying the right message. Finally, Windows Vista seems to have an answer to this. And it is aptly called the Task Dialog. It is worth paying attention to the name. It is no longer a dumb, unintelligible Message box. It is a dialog, an interactive one, and one whose aim is to get some meaningful task done.

There are two new APIs in Vista (implemented in comctl32.dll) related to Task Dialogs: TaskDialog and TaskDialogIndirect. The TaskDialog API is pretty straightforward and it has a small improvement over the MessageBox API. In this article, you look at the more powerful API, TaskDialogIndirect.

The API, the Dialog, and the Parts

The API you are interested in is TaskDialogIndirect. This is defined in the new commmctrl.h and the signature of this function is like that below:

HRESULT TaskDialogIndirect(
   [in] const TASKDIALOGCONFIG *pTaskConfig,
   [out] int *pnButton,
   [out] int *pnRadioButton,
   [out] BOOL *pfVerificationFlagChecked
);

The most glaring difference between this and the MessageBox API is that, unlike MessageBox, the return value of TaskDialogIndirect is NOT the option the user selected. Instead, the API returns a more generic HRESULT and if the HRESULT did indicate success, the options selected (note the plural) are returned in the three out values (pnButton, pnRadioButton, and pfVerificationFlagChecked).

The TASKDIALOGCONFIG is where you would let Vista know how the task dialog is to be presented. It is powerful enough to meet most of your needs addressed and, I think, this had been a long time coming. The structure is shown below for convenience.

typedef struct _TASKDIALOGCONFIG {
   UINT cbSize;
   HWND hwndParent;
   HINSTANCE hInstance;
   TASKDIALOG_FLAGS dwFlags;
   TASKDIALOG_COMMON_BUTTON_FLAGS dwCommonButtons;
   PCWSTR pszWindowTitle;
   union {
      HICON hMainIcon;
      PCWSTR pszMainIcon;
   };
   PCWSTR pszMainInstruction;
   PCWSTR pszContent;
   UINT cButtons;
   const TASKDIALOG_BUTTON *pButtons;
   int nDefaultButton;
   UINT cRadioButtons;
   const TASKDIALOG_BUTTON *pRadioButtons;
   int nDefaultRadioButton;
   PCWSTR pszVerificationText;
   PCWSTR pszExpandedInformation;
   PCWSTR pszExpandedControlText;
   PCWSTR pszCollapsedControlText;
   union {
      HICON hFooterIcon;
      PCWSTR pszFooterIcon;
   };
   PCWSTR pszFooter;
   PFTASKDIALOGCALLBACK pfCallback;
   LONG_PTR lpCallbackData;
   UINT cxWidth;
} TASKDIALOGCONFIG;

One minor observation here. All text data are strictly widechar strings now. Unlike other windows structures that normally had an ANSI and a UNICODE option, there is no such thing here. The data has to be UNICODE.

First, you should get familiar with all the parts that make up the new Task Dialog. The pictures below are two samples of task dialogs, and have the different parts marked for convenience. Each part is represented by a member variable of the TASKDIALOGCONFIG structure.

Member VariablePart Number in SampleNotes
hWndParent - Similar to MessageBox, specifies the parent window for the task dialog
hInstance - This is new. Note that all the text resource and icon resource members in this structure can simply indicate the resource ID, in which case one would have to specify the module handle that contains these resources in the hInstance member. This helps very much in localization and one would not have to do all the LoadString and LoadIcon; the API takes care of all this.
dwCommonButtons 1 This is a set of flags that indicate which one of the standard buttons is to be shown. The options are OK, Yes, No, Cancel, Retry, and Close. The text for these buttons is fixed.
pszWindowTitle 2 Equivalent to the caption of MessageBox, used to specify something related to the task. Typically, the application name is shown here.
hMainIcon,pszMainIcon 3 An icon to associate the task dialog with. Like MessageBox, one can associate this with a stop, information, or warning icon.
pszMainInstruction 4 Area used to convey the main message. This should be as crisp and clear as possible.
pszContent 5 Area used to convey supplementary information that could provide additional information to user. This is not as prominent as the main instruction.
pButtons 6 Any user-specified buttons that are to be shown on the dialog. The TASKDIALOG_BUTTON structure allows one to specify a command ID and text to be associated with each of the buttons.
pRadioButtons 7 Any user-specified radio buttons that are to be shown on the dialog. The TASKDIALOG_BUTTON structure allows one to specify a command ID and text to be associated with each of the radio buttons.
pszVerificationText 8 One of the cool things and a very useful feature is to have a check box. Quite often one would have had a situation where showing a message box repeatedly could be an annoyance and one wished there was an option to let the user turn it off. Here it is. The verification text is just that. It is a text shown alongside a checkbox and the API returns this setting to user in its pfVerificationFlagChecked out parameter.
pszExpandedInformation 9 Additional information that can be shown or hidden via a button. Again, intended to reduce clutter and show information only when requested.
pszExpandedControlText 10 Text to be shown alongside an expand/collapse button when in expanded state.
pszCollapsedControlText 11 Text to be shown alongside an expand/collapse button when in collapsed state.
pszFooter 13 A footer that could contain more information.

Task Dialogs in Vista

Questions and Sample Codes

Having seen the API and the structure, you see that usage of this API is pretty straightforward. So, I thought it would make more sense to make this a question and answer kind of thing to come up with a requirement and then write code to get the desired result.

Before you go ahead, make sure you have downloaded the latest Vista SDK and have set the environment for compiling under Visual Studio. You can find the SDK here.

Question 1: I need a dialog that prompts the user to install a particular software. How can I make the experience more user-friendly and have custom text for the buttons, say Install and Do not install?

Answer: Okay. Start like this. You need a main instruction. You need some content. You need a window title. You need two user-defined buttons. You do NOT need any standard buttons. Now, write code for this:

int nResult = 0;

TASKDIALOGCONFIG stTaskConfig;
ZeroMemory(&stTaskConfig,sizeof(stTaskConfig));

stTaskConfig.cbSize = sizeof(TASKDIALOGCONFIG);
stTaskConfig.hwndParent = NULL;
stTaskConfig.hInstance = NULL;

stTaskConfig.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION;

stTaskConfig.pszWindowTitle = L"Our Application";
stTaskConfig.pszMainInstruction = L"Do you want to install messenger
                                    client?";
stTaskConfig.pszContent = L"Messenger client is needed to
                            communicate with third party applications.";

#define ID_BUTTON_INSTALL             1000
#define ID_BUTTON_DO_NOT_INSTALL      1001

stTaskConfig.cButtons = 2;
TASKDIALOG_BUTTON buttons[] = { 
   {ID_BUTTON_INSTALL,L"Install"},
   {ID_BUTTON_DO_NOT_INSTALL,L"Do not install"}
   };
stTaskConfig.pButtons = buttons;
stTaskConfig.nDefaultButton = ID_BUTTON_INSTALL;

if(SUCCEEDED(TaskDialogIndirect(&stTaskConfig,&nResult,NULL,NULL)))
{
   //API call succeeded, now , check return values
   if(ID_BUTTON_INSTALL == nResult)
   {
      //install application
   }
   else
   {
      //do not install application
   }
}

The result looks like below:

[Question1.jpg]

Question 2: Hm... That was simple. Now, typically, administrator rights are required for installing applications. How do I give feedback to user about this? Is there something in here I can use to do this?

Answer: That's a good thought. With Vista, the standard way to do this is to show a shield icon when some action kicks off a UAC permission and approval process. Designers of the task dialog have thought about this. And, they have just that. It can be done in two ways: show a shield icon for the whole task dialog, or, show a shield icon only for the install button. See how you can achieve the first approach. The code you add is as shown below (in bold). You just specify the icon to use in the pszMainIcon member of the stTaskConfig structure.

.......
stTaskConfig.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION;
stTaskConfig.pszMainIcon = MAKEINTRESOURCE(TD_SHIELD_ICON);
.......

The result looks like that below. Note the shield icon:

[Question2.jpg]

Question 3: Wow! That turned out to be simpler than I thought. Is it possible to add some additional information or version of the software and the company that produces it, like a footnote?

Answer: Yes, of course. The TasKDIALOGCONFIG structure has provision for that. All you have to do is set this information in the pszFooter member variable.

.......
stTaskConfig.pszFooter = L"Messenger client 1.0.0.1,
                           Messenger Client Software Inc.";

if(SUCCEEDED(TaskDialogIndirect(&stTaskConfig,&nResult,NULL,NULL)))
.......

The result looks like below. Note the footnote showing the version and company information:

[Question3.jpg]

Question 4: Great! How about this? Can I show this footnote only if needed? You see, like clicking on a More information button would show this information. Otherwise, the footnote is not shown.

Answer: The TASKDIALOGCONFIG structure has provision for that too. There are four member variables that touch upon this feature. First, pszExpandedInformation. This has information on expandable text that is shown only if needed. The footer note you added does not have a hide/show option and is always shown. Remove it because it doesn't fit your requirements. Instead, add pszExpandedInformation to the footer area. The TDF_EXPAND_FOOTER_AREA flag has to be specified to do this. Next, there is the button text to be used for the button used to expand/collapse this expandable information. The member variables are pszExpandedControlText and pszCollapsedControlText. The code to do all this looks like below:

.......

//stTaskConfig.pszFooter = L"Messenger client 1.0.0.1,
   Messenger Client Software Inc.";

stTaskConfig.pszExpandedInformation  = L"Messenger client 1.0.0.1,
   Messenger Client Software Inc.";
stTaskConfig.pszExpandedControlText  = L"Show concise";
stTaskConfig.pszCollapsedControlText = L"More information";
stTaskConfig.dwFlags |= TDF_EXPAND_FOOTER_AREA;

if(SUCCEEDED(TaskDialogIndirect(&stTaskConfig,&nResult,NULL,NULL)))
.......

The result looks like below. Note the Concise Info button that collapses the footer area:

[Question4.jpg]

Question 5: Neat! Say, the user selected Do not install for some reason. Now, when the application does this check again, I don't want to prompt the user again to install this. I want an option that would allow the user to say, Enough! I know what I am doing; I have seen this message once. Stop showing me this message again. Is it possible?

Answer: Sure. All you need to do is specify the text for the pszVerificationText and you will see a neat little check box appear that the user can check/uncheck before dismissing the dialog. You also can specify whether this should be checked or unchecked by default by setting or removing the TDF_VERIFICATION_FLAG_CHECKED flag. The code to do all this looks like below:

.......

stTaskConfig.pszCollapsedControlText = L"More information";
stTaskConfig.pszVerificationText     = L"Do not prompt me again";

BOOL bVerificationChecked = FALSE;
if(SUCCEEDED(TaskDialogIndirect(&stTaskConfig,&nResult,NULL,
                                &bVerificationChecked)))
{
   if(bVerificationChecked)
   {
      //set some registry to indicate so, so that next time
      //the prompt is not shown
   }
   else
   {
      //clear the registry, so next time the prompt is shown
   }
}
.......

The result looks like below. Note the Do not prompt me again check box:

[Question5.jpg]

Question 6: This messenger client software comes with multiple options: a single user license one and a network license one. I should be able to distinguish between these. How do I go about doing that?

Answer: You could do this in two ways. You can have three buttons, one each for Install single user, Install multiclient, and Do not install. You already have looked at this. There is one other way you can do this: via radio buttons. Just as you added your own buttons, you can add your own radio buttons, too, by using the pRadioButtons member variable. You can specify what radio button should be selected by default by using the nDefaultRadioButton member variable. The selected radio button is returned via the third parameter to the TaskDialogIndirect API. The code to do all this looks like below:

.......

#define ID_RADIO_BUTTON_SINGLE_USER      1002
#define ID_RADIO_BUTTON_NETWORK          1003

TASKDIALOG_BUTTON radio_buttons[] = {
   {ID_RADIO_BUTTON_SINGLE_USER,L"Install single user client
                                  software"},
   {ID_RADIO_BUTTON_NETWORK,L"Install network client software"}
   };
stTaskConfig.cRadioButtons       = 2;
stTaskConfig.pRadioButtons       = radio_buttons;
stTaskConfig.nDefaultRadioButton = ID_RADIO_BUTTON_SINGLE_USER;

int nRadioOption;

if(SUCCEEDED(TaskDialogIndirect(&stTaskConfig,&nResult,&nRadioOption,
                                &bVerificationNeeded )))
{
   //API call succeeded, now , check return values
   if(ID_BUTTON_INSTALL == nResult)
   {
      //install application

      //first check for option selected
      if(ID_RADIO_BUTTON_SINGLE_USER == nRadioOption)
      {
         //install single user license software
      }
      else
      {
         //install multi user network software
      }
   }
   else
   {
      //do not install application
   }
}
.......

The result looks like below. Note the radio buttons:

[Question6.jpg]

Task Dialogs in Vista

Question 7: Hm... Looks good. However, I would have preferred that the user could simply select a button instead of having to choose a radio button and hit Install. Is there a a way other than having all those buttons lined up at the bottom?

Answer: There sure is. There are what are called command links. These are new in Vista, too. They are regular buttons, but with more information. One can have text and a note associated with it to give some more information as to what clicking the button would result in. Fortunately, you can have command links in your Task dialog. You can get rid of the radio buttons altogether. You can replace the Install and Do not install buttons with Install single user license, Install multi user license, and Do not install command links by using the code below. The key is to specify the TDF_USE_COMMAND_LINKS flag. The code to do all this looks like below:

stTaskConfig.cbSize      = sizeof(TASKDIALOGCONFIG);
stTaskConfig.hwndParent  = NULL;
stTaskConfig.hInstance   = NULL;

stTaskConfig.dwFlags     = TDF_ALLOW_DIALOG_CANCELLATION;
stTaskConfig.pszMainIcon = MAKEINTRESOURCE(TD_SHIELD_ICON);

stTaskConfig.pszWindowTitle = L"Our Application";
stTaskConfig.pszMainInstruction = L"Do you want to install messenger
                                    client?";
stTaskConfig.pszContent = L"Messenger client is needed to communicate
                            with third party applications.";

#define ID_BUTTON_INSTALL_SINGLE_USER        1000
#define ID_BUTTON_INSTALL_MULTI_USER         1001
#define ID_BUTTON_DO_NOT_INSTALL             1002

stTaskConfig.cButtons = 3;
TASKDIALOG_BUTTON buttons[] = { 
   {ID_BUTTON_INSTALL_SINGLE_USER,L"Install single user client
      software\nUse this option if you are the only one using the
      messenger client"},
   {ID_BUTTON_INSTALL_MULTI_USER,L"Install network client
      software\nUse this option to allow others on your network to
      use the messenger client"},
   {ID_BUTTON_DO_NOT_INSTALL,L"Do not install"}
   };
stTaskConfig.pButtons = buttons;
stTaskConfig.nDefaultButton = ID_BUTTON_INSTALL_SINGLE_USER;
stTaskConfig.dwFlags |= TDF_USE_COMMAND_LINKS;

stTaskConfig.pszExpandedInformation  = L"Messenger client 1.0.0.1,
   Messenger Client Software Inc.";
stTaskConfig.pszExpandedControlText  = L"Show concise";
stTaskConfig.pszCollapsedControlText = L"More information";
stTaskConfig.dwFlags |= TDF_EXPAND_FOOTER_AREA;

BOOL bVerificationNeeded = FALSE;
stTaskConfig.pszVerificationText = L"Do not prompt me again";

if(SUCCEEDED(TaskDialogIndirect(&stTaskConfig,&nResult,NULL,
                                &bVerificationNeeded )))
{
   //API call succeeded, now , check return values
   if(ID_BUTTON_INSTALL_SINGLE_USER == nResult)
   {
      //install single user application
   }
   else if(ID_BUTTON_INSTALL_MULTI_USER == nResult)
   {
      //install multi user application
   }
   else
   {
      //do not install application
   }
}

The result looks like below. Note the following: The moment you added the TDF_USE_COMMAND_LINKS flag, the buttons at the bottom have all moved to the main area. Note the look of the new buttons. There is a bold text in a bigger font, which is used to convey the main information. And, below that, is text in smaller font, used to convey any additional information. This is done by separating the two text fields by a new line character as shown in the code.

[Question7.jpg]

Question 8: Can I also add hyperlinks? Say, I wanted to provide a hyperlink for the messenger client inc. company?

Answer: Sure. All you have to do is specify the TDF_ENABLE_HYPERLINKS flag and add the HTML <a href> tag for the hyperlink as a part of the text. For example, if you wanted to add the hyperlink to the messenger client inc. company, you would change the pszExpandedInformation like below: The code to do all this looks like below:

...
stTaskConfig.dwFlags |= TDF_ENABLE_HYPERLINKS;
stTaskConfig.pszExpandedInformation = L"Messenger client 1.0.0.1,
   <a href=\"http://www.messengerclientinc.com/\">Messenger Client
   Software Inc.</a>";
...

The result looks like below. Note the hyperlink.

[Question8.jpg]

Question 9: I see the hyperlink, but...?

Answer: I know... I know what you are going to say. Nothing happens when you click the hyperlink. Is that right? That brings you to the member variable pfCallback. The taskdialog uses a callback mechanism to let application developers implement the action on clicking a hyperlink. For this, you have to supply a callback with a specific signature in the TASKDIALOGCONFIG structure. When certain events occur, you will receive a callback. For example, when a hyperlink is clicked, you will receive a callback with notification code TDN_HYPERLINK_CLICKED. Here's how this works. The callback is implemented like below to handle TDN_HYPERLINK_CLICKED notification:

HRESULT CALLBACK TaskDialogCallbackProc(          HWND hwnd,
   UINT uNotification,
   WPARAM wParam,
   LPARAM lParam,
   LONG_PTR dwRefData
)
{
   if(TDN_HYPERLINK_CLICKED == uNotification)
   {
      //get the URL and launch a browser
      ShellExecuteW(NULL,L"open",(LPWSTR)lParam,NULL,NULL,S
                    W_SHOWNORMAL);
   }
   return S_OK;
}

And here is the code that sets the callback"

...
stTaskConfig.pfCallback = &TaskDialogCallbackProc;
...

You should now see that a browser is launched when you click the hyperlink.

Question 10: Earlier, it was mentioned that the shield icon can be shown for specific buttons only instead of having it shown for the whole task dialog. How can I do this?

Answer: You can do this. Comment out the pszMainIcon member setting. Instead, use the callback function. As soon as the task dialog is created, the callback function is called with notification code TDN_CREATED. This is a chance for you to make modifications to the buttons. For example, to set the shield icon for say, the two installation buttons, what you need to do is send a TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE message to the task dialog window specifying the button ID for which you want to set the shield icon. Sample code is shown below:

HRESULT CALLBACK TaskDialogCallbackProc(          HWND hwnd,
   UINT uNotification,
   WPARAM wParam,
   LPARAM lParam,
   LONG_PTR dwRefData
)
{
   if(TDN_HYPERLINK_CLICKED == uNotification)
   {
      //get the URL and launch a browser
      ShellExecuteW(NULL,L"open",(LPWSTR)lParam,NULL,NULL,
                    SW_SHOWNORMAL);
   }
   else if(TDN_CREATED == uNotification)
   {
      SendMessage(hwnd,TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE,
                  ID_BUTTON_INSTALL_SINGLE_USER,1);
      SendMessage(hwnd,TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE,
                  ID_BUTTON_INSTALL_MULTI_USER,1);
   }
   return S_OK;
}

Also, remove the pszMainIcon setting:

...
//stTaskConfig.pszMainIcon = MAKEINTRESOURCE(TD_SHIELD_ICON);
...

The result looks like below. Note the shield icons on the two command links.

[Question9.jpg]

References



Comments

  • There are no comments yet. Be the first to comment!

Top White Papers and Webcasts

  • Entire organizations suffer when their networks can't keep up and new opportunities are put on hold. Waiting on service providers isn't good business. In these examples, learn how to simplify network management so that your organization can better manage costs, adapt quickly to business demands, and seize market opportunities when they arise.

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date