Contents
Introduction
If you've read my first two articles on Vista task dialogs (Part 1, Part 2), you know that task dialogs are a great new UI feature, but they can
be cumbersome to use. At PDC 2005, session PRS319 ("Building Applications That Look Great in Windows Vista"),
the speaker demonstrated a tool called TDPad
that was developed by the shell team. TDPad is a simple task dialog editor that uses XML data files internally
and does some of the grunt work, but it still takes a lot of manual labor to incorporate the output from TDPad
into your app.
This article presents the TDXML (XML-based task dialogs) library, which is designed to be much easier to use
compared to calling the TaskDialog()
and TaskDialogIndirect()
APIs directly. It also
includes a visual task dialog editor, which you can use to generate the XML that is then used as input to TDXML.
You can of course create the XML yourself, but I think you'll find that the instant feedback that the visual editor
provides to be far more developer-friendly.
The TDXML library's design goals are:
- Completely data-driven: You don't have to change any C++ code to change the appearance of a task dialog.
- Uses a widely-available XML parser: TDXML uses the MSXML parser, which is present on all modern systems.
- Localizable: The XML can be stored as a file along side your application, or put in a resource. Either way,
it allows for easy localization. The data-driven nature of the library means you can localize the XML without touching
or recompiling any C++ code.
- Easy on the caller: To use a task dialog, you don't have to do anything more complex than initialize a C++
object and call
DoModal()
, similar to how you use CDialog
in MFC and CDialogImpl
in ATL.
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. The TDXML library uses the CString
and _bstr_t
string wrapper classes; I have only tested the code on VC 2005 in a WTL application, but
since those string classes are available to MFC as well, the TDXML library should be easy to incorporate into an
MFC app.
Using the Visual Editor
Overview
The task dialog editor, included in the demo download in the TDEditor project, is a tool you can use
to set up a task dialog, see exactly how it will look in your program, and then generate an XML file that you can
use with the TDXML library. When you run TDEditor, you'll see four pages where you can set up various UI elements
of the task dialog. Here is how it looks initially (screen shots of the editor are shown as thumbnails in order
to stay within CodeProject's limit on article width):
TDEditor also shows a live preview of what the task dialog will look like, using the current text and settings
that you have entered in the editor. This is a real task dialog, not an emulation, so you can be confident that
what you see is what you will get. Here's the initial state of the task dialog upon running TDEditor:
In most cases, TDEditor automatically updates the task dialog to reflect any changes you make in the editor,
but there are a few settings where this isn't possible. When you change such a setting, an information icon is
added to the Rebuild Dialog button to let you know that you need to rebuild the dialog to see your changes.
If you make changes in the task dialog itself, such as clicking the expand/collapse button or selecting a radio
button, you can discard those changes by clicking Rebuild Dialog. Click the thumbnail below to see a sample
animation showing the live editing in action:
Once you have the task dialog set up to your satisfaction, you can generate an XML file that contains all the
information that you entered into the editor. First, click Save XML to bring up a file save dialog. Select
the filename to use, and TDEditor will generate an XML file for you. You can then use that XML as input to TDXML,
as described later in this article.
Adding Text and Icons
The Text and Icons page is where you set up the static text elements of the task dialog. The elements
that have check boxes beside them are optional; if an element is unchecked, it will not be present in the task
dialog. The exception is the Caption element -- if you uncheck Caption, Windows will provide
a default caption. The default caption text in Vista is the name of the executable that is showing the dialog.
To demonstrate the editor in action, I'll show how to build a dialog similar to the one in my
earlier article on using TaskDialogIndirect. After setting up the basic text elements, here is how the editor
looks:
Here is how the task dialog looks so far:
Notice that the footer text contains an <a>
tag, but it is not shown as a hyperlink. Don't
worry about that for now, we'll fix that up later.
Adding Push Buttons
The Buttons page lets you specify which buttons you want to appear in the task dialog. You can add any
of the six built-in buttons, or you can add any number of additional buttons with custom text. You can also choose
whether the custom buttons will appear as regular push buttons or command links.
For our sample dialog, we'll add a Close button and two custom buttons. Use the buttons to the right of the
Custom buttons list to add, remove, or edit the dialog's custom buttons. When you add or edit a custom button,
TDEditor displays a dialog where you enter the button's details:
If you are using command links, as we are in this sample, you can enter additional text in the Line 2 text
edit box. This text will be shown on the second line of the button in a smaller font.
You can change the ID to any value, but be aware that small numbers may conflict with the IDs of built-in buttons.
The default ID is 1000, but any number greater than 100 will work fine. TDEditor will automatically increment the
ID for each additional button, and you can also click the Reset IDs button in the editor to reassign sequential
IDs to all custom buttons.
Here is the Buttons page showing the buttons that we want in the task dialog:
And here is the task dialog with the new buttons:
Adding Radio Buttons
The Radio Buttons page works similarly to the Buttons page. Since there are no built-in radio
buttons, this page only has a Custom buttons list and the associated controls. The Default button
setting also works a bit differently -- it controls which radio button will be checked when the dialog is
first shown. If you don't want any of the radio buttons to start out checked, uncheck the Default button
setting.
This task dialog for our fictional AntiFluff program wouldn't normally have radio buttons, but to illustrate
the feature, I'll add two buttons as shown here in the editor:
And here's the task dialog. Notice how the second radio button is checked, in accordance with the Default
button setting in the editor.
Setting Dialog Flags
The Flags page is pretty simple, and lets you toggle some task dialog options and set a couple of additional
properties. The marquee progress bar style also has a timer setting that defaults to 50 ms. This is the amount
of time between progress bar updates; a smaller value creates a faster-moving marquee.
Here are the flags that we'll set on our sample task dialog:
And here's the final version of the task dialog:
Notice that the footer now contains a hyperlink, since we set the Allow hyperlinks flag.
Showing a Task Dialog with TDXML
Your interface to the TDXML library is the CTaskDialogXML
class, which is in the TaskDialogXML.cpp
and TaskDialogXML.h files. Using CTaskDialogXML
involves just two steps:
- Initialize the dialog by telling it where to find the XML that describes the contents of the dialog.
- If initialization succeeds, call
DoModal()
to show the dialog.
There are three methods for initializing the dialog, each one reads the XML data from a different source:
bool InitFromFile ( LPCTSTR szFilename )
- Call
InitFromFile()
to initialize the dialog using an XML file. szFilename
is the
full path to the file.
bool InitFromResource ( resourceID, resourceType, hModule )
- Call
InitFromResource()
to initialize the dialog using data stored in a resource. resourceID
can be either a numeric or a string ID. resourceType
defaults to "TDXML"; you can put your
XML data files in this custom resource type to keep them grouped together in the resources of your binaries. hModule
is the HMODULE
that contains the resource. If you don't specify hModule
, the executable
for the current process is used.
bool InitFromVariant ( const _variant_t variantData )
InitFromVariant()
is the function that the other two initialization methods call, but you can
call it directly if you have a VARIANT
or _variant_t
that holds a data source. Any data
that can be passed to IXMLDOMDocument::load()
is acceptable.
CTaskDialogXML
uses a callback function to implement some features, like buttons with UAC shields,
that are not built into the task dialog API. This means that if you need to use your own callback function, you
must set two members of your CTaskDialogXML
object that do the equivalent of the pfCallback
and lpCallbackData
members of TASKDIALOGCONFIG
. The members are m_pfCallback
and m_lpCallbackData
; when you set them, CTaskDialogXML
will call your callback just
like TaskDialogIndirect()
would.
After initializing the CTaskDialogXML
object, call DoModal()
to show the dialog:
HRESULT DoModal ( HWND hwndParent )
DoModal()
returns E_FAIL
if the dialog was not initialized or initialization failed,
or the return value from TaskDialogIndirect()
otherwise.
If DoModal()
returns a successful HRESULT
, you can read three public members of the
CTaskDialogXML
object to see what actions the user took in the dialog:
m_nButton
- The ID of the push button that the user clicked to close the dialog.
m_nRadioButton
- The ID of the radio button that was checked when the dialog was closed.
m_bChecked
- A
BOOL
indicating the state of the check box when the dialog was closed.
The latter two of those members will not contain meaningful data if the corresponding UI element was not included
in the dialog. See the documentation on TaskDialogIndirect()
for more details about how those values
are set.
The TDXMLTester project in the downloadable code contains three task dialogs, and demonstrates various
ways to use CTaskDialogXML
. The first dialog uses an external file called testdialog.xml, which
must be in the same directory as TDXMLTester.exe. A sample file is included in the source download, or you
can create and use your own file if you like.
Here is the code from TDXMLTester that uses an external XML file:
void CMainDlg::OnShowDialog1 (
UINT uCode, int nID, HWND hwndCtrl )
{
HRESULT hr;
tdxml::CTaskDialogXML dlg;
TCHAR szTestFilename[MAX_PATH];
GetModuleFileName ( NULL, szTestFilename,
_countof(szTestFilename) );
PathRemoveFileSpec ( szTestFilename );
PathAppend ( szTestFilename, _T("testdialog.xml") );
if ( dlg.InitFromFile ( szTestFilename ) )
hr = dlg.DoModal ( m_hWnd );
}
As you can see, there isn't much to it. The code builds the full path to the XML file, then calls InitFromFile()
and passes that path. If InitFromFile()
succeeds, the code then calls DoModal()
.
The other two dialogs use XML contained in resources in TDXMLTester.exe. If you look at the TDXMLTester
resources, you'll see two nodes under the "TDXML" type:
Test dialog 2 uses the IDR_CHNXML
resource, like this:
void CMainDlg::OnShowDialog2 (
UINT uCode, int nID, HWND hwndCtrl )
{
HRESULT hr;
tdxml::CTaskDialogXML dlg;
if ( dlg.InitFromResource ( IDR_CHNXML ) )
hr = dlg.DoModal ( m_hWnd );
}
The last dialog is very similar, it just uses a string resource ID instead of a numeric ID. Dialog 3 also shows
how to use a custom callback function to handle the notification sent when the user clicks a hyperlink.
void CMainDlg::OnShowDialog3 (
UINT uCode, int nID, HWND hwndCtrl )
{
HRESULT hr;
tdxml::CTaskDialogXML dlg;
if ( dlg.InitFromResource ( _T("BIGTESTDIALOG") ))
{
dlg.m_pfCallback = TDCallback;
dlg.m_lpCallbackData = (LONG_PTR) this;
hr = dlg.DoModal ( m_hWnd );
}
}
Other CTaskDialogXML Details
CTaskDialogXML
contains a public member m_td
, which is the TASKDIALOGCONFIG
struct that is passed to TaskDialogIndirect()
. You can modify this struct before calling DoModal()
if you want to do any additional initialization or setup steps.
CTaskDialogXML::DoModal()
has two ways of calling TaskDialogIndirect()
. The default
behavior is to call the API directly, which requires that your application be statically linked to the Vista version
of comctl32.dll. If you don't want to have that dependency (for example, you want your program to run on
XP as well), you can define the TDXML_DONT_LINK_TO_API
symbol in your stdafx.h. This makes
DoModal()
use GetProcAddress()
to get the address of TaskDialogIndirect()
.
Note that this method still requires your app to have comctl32.dll loaded into its process, but if the OS
does not support task dialogs, DoModal()
will simply return E_FAIL
.
Bugs and Feature To-Do List
Here are the bugs (that I know about!) and some features that I have in mind for the future. If you have any
other feature requests or bug reports, don't hesitate to post on this article's forum.
In the task dialog editor:
- You can't set the base ID for buttons when using the Reset IDs command.
- The editor doesn't support loading a previously-generated XML file.
- The standard dialog navigation keys don't work (Tab, Alt+letter, etc.).
In the TDXML library:
- The XML used to build a task dialog doesn't support loading string or icon resources. You can work around this
limitation by modifying the
m_td
member as necessary before calling DoModal()
.
- Fix any problems related to using the class with MFC.
- The default module for loading resources should be different based on whether the app is using MFC or ATL.
Currently, it always uses NULL, which is the API-level default -- see the
FindResource()
docs
for more info.
Further Reading
Vista Goodies in C++: Showing Friendly Messages with Task Dialogs
Vista Goodies in C++: Using TaskDialogIndirect to Build Dialogs that Get
User Input
Windows
Vista for Developers - Part 2 - Task Dialogs in Depth. Kenny Kerr has a really long post talking about more
features of task dialogs, along with lots of sample code and an ATL wrapper class.
MSDN has some lengthy pages on the new
Vista UI guidelines. For task dialogs, the page to be familiar with is Text
and Tone.
Copyright and License
This article is copyrighted material, ©2007 by Michael Dunn, all rights reserved. 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
benefitting from my code) but is not required. Attribution in your own source code is also appreciated but not
required.
Revision History
March 19, 2007: Article first published.