Windows with C++

Windows Template Library 8.0

Kenny Kerr

Code download available at:WindowsWithC++2007_12.exe(175 KB)

Contents

Task Dialogs
Aero Wizards
New File Dialogs
Other WTL Features to Explore

The developers responsible for the Windows® Template Library (WTL) recently released a new version of this excellent library that fully supports virtually all of the new user interface features and enhancements introduced with Windows Vista®. This month I'll be highlighting some of the new features in this latest release. In case you are not familiar with WTL and how it fits into the Visual C++® development landscape, I will start with a brief introduction.

The Visual C++ team developed the Active Template Library (ATL) to allow COM clients and servers to be easily created while producing the smallest and fastest code possible. Unlike MFC, which is focused on developing user interface applications and which only later added support for COM, ATL was designed from the start to enable COM development and it does this well. It supported only rudimentary user interface development but the classes it provided were incredibly useful as lightweight building blocks and base classes for a more sophisticated user interface library.

WTL extends ATL with a very rich set of class templates for building applications as simple or as complex as you can imagine. WTL is not included with Visual C++ but is made available both on the SourceForge Web site at sourceforge.net/projects/wtl and through the Microsoft Download Center.

WTL 8.0 was primarily developed with a focus on Windows Vista UI support so I will cover some topics that I've covered in previous columns and articles. But the focus this month is on how the new WTL improvements will accelerate adoption of these new features and ensure that you have even less code you need to write yourself! The download for this column, available from the MSDN® Magazine Web site, provides examples of most of the features described here.

Task Dialogs

Task dialogs are one of my favorite new features in Windows Vista, providing a powerful API for developing both simple and sophisticated dialogs with very little effort.

The TaskDialogIndirect function and the TASKDIALOGCONFIG structure can be quite daunting and well-deserving of some help from C++ to simplify its use. WTL provides support for task dialogs via the CTaskDialogImpl base class template. Internally the CTaskDialogImpl class uses the CTaskDialogConfig class to wrap the TASKDIALOGCONFIG structure. This is exposed through the public m_tdc variable should you need to access it directly, but you're unlikely to need it as the CTaskDialogImpl class does an excellent job of providing suitable member functions that wrap it up for you. The CTaskDialog concrete class that derives from CTaskDialogImpl is also provided for very simple scenarios, but it is usually best simply to derive a class from CTaskDialogImpl to implement your task dialog.

Figure 1 illustrates a number of task dialog features and how you can make use of them from WTL. The example sets the window title, main instruction and content text captions. Task dialogs support a number of additional text captions, though they require different mechanisms to set them depending on whether the task dialog has been constructed or not. Unfortunately, the CTaskDialogImpl class does not abstract these differences and this can make updating these text captions a bit confusing. Figure 2 summarizes how to set the various text captions using the CTaskDialogImpl class depending on whether it is set before or after the task dialog is constructed. Typically you use the pre-construction method in your task dialog's constructor and the post-construction method in various event handlers to update the captions.

Figure 2 Updating Task Dialog Captions

Text Caption Pre-Construction Post-Construction
Window title SetWindowTitle() ::SetWindowText()
Main instruction SetMainInstructionText() SetElementText(TDE_MAIN_INSTRUCTION, ...
Content SetContentText() SetElementText(TDE_CONTENT, ...
Footer SetFooterText() SetElementText(TDE_FOOTER, ...
Expanded information SetExpandedInformationText() SetElementText(TDE_EXPANDED_INFORMATION, ...
Expanded control text SetExpandedControlText() N/A
Collapsed control text SetCollapsedControlText() N/A
Verification SetVerificationText() N/A

Figure 1 Task Dialog Example

enum { Button_Save = 101, Button_DontSave, }; class SampleDialog : public CTaskDialogImpl<SampleDialog> { public: SampleDialog() { SetWindowTitle(L"Title"); SetMainInstructionText(L"Main instruction"); SetContentText(L"Content"); ModifyFlags(0, TDF_ALLOW_DIALOG_CANCELLATION | TDF_USE_COMMAND_LINKS); static TASKDIALOG_BUTTON buttons[] = { { Button_Save, L"&Save", }, { Button_DontSave, L"Do&n't save", }, }; SetButtons(buttons, _countof(buttons)); } };

Figure 1 Task Dialog Example

Figure 1** Task Dialog Example **

The code in Figure 1 also makes use of the ModifyFlags method. Various flags are documented in the Windows SDK for customizing the task dialog's appearance. The ModifyFlags method's first parameter indicates the flags to remove and the second parameter indicates the flags to add. The SetButtons method is also used to specify the buttons that will be displayed on the task dialog. The example declares the array of TASKDIALOG_BUTTON structures static to ensure that its storage lives beyond the constructor. This is critical since the button definitions are referenced later on when the DoModal method is called that finally passes all of the information to the TaskDialogIndirect function to actually display the task dialog.

Calling the DoModal method is straightforward and can optionally provide the identifier of the button the user selected. Figure 3 shows an example of how you might display the task dialog from Figure 1 and handle the various button identifiers.

Figure 3 Using DoModal to Call the Dialog Function

SampleDialog dialog; int selectedButtonId = 0; COM_VERIFY(dialog.DoModal( 0, // parent window &selectedButtonId)); switch (selectedButtonId) { case Button_Save: { // TODO: save break; } case Button_DontSave: { // TODO: don't save break; } case IDCANCEL: { // TODO: cancel break; } default: { ASSERT(false); } }

Alternatively, you can use the task dialog's callback facility that the CTaskDialogImpl class exposes through a set of public methods that you can optionally implement in your derived class to handle different events. For example, in order to handle button clicks within your dialog class, you can add the public method shown in Figure 4.

Figure 4 Handling Click Events

class SampleDialog : public CTaskDialogImpl<SampleDialog> { public: ... bool OnButtonClicked(int buttonId) { bool closeDialog = false; switch (buttonId) { // Do something here } return !closeDialog; } };

Handling this event is especially useful when you want to handle a particular button click but don't necessarily want the task dialog to close. There are also quite a few other events that can be handled in a similar way. Figure 5 shows a list of overrides that the CTaskDialogImpl class provides. Remember that these are ATL-style overrides that are evaluated at compile-time, so they do not require virtual function calls.

Figure 5 CTaskDialogImpl Overrides

Method Description
void OnDialogConstructed() Task dialog created but not yet visible. Window handle is valid.
bool OnButtonClicked(int buttonId) Button or command link clicked. Return true to prevent dialog from closing.
void OnRadioButtonClicked(int buttonId) Radio buttons clicked.
void OnHyperlinkClicked(PCWSTR url) Hyperlink clicked.
void OnExpandoButtonClicked(bool expanded) Expando button clicked.
void OnVerificationClicked(bool checked) Verification checkbox was checked or cleared.
void OnHelp() F1 key pressed.
bool OnTimer(DWORD milliseconds) Sent every 200 milliseconds. Parameter indicates milliseconds since timer started or reset.
void OnNavigated() Navigation has occurred.
void OnDestroyed() Task dialog destroyed. Window handle no longer valid.

Task dialogs provide many more features. The sample code for this column provides a comprehensive set of examples illustrating virtually all of the task dialog's capabilities with the help of WTL. For a detailed look at the extensive capabilities of the task dialog API, read my blog posting "Windows Vista for Developers–Part 2–Task Dialogs in Depth" (weblogs.asp.net/kennykerr/archive/2006/07/18/Windows-Vista-for-Developers-_1320_-Part-2-_1320_-Task-Dialogs-in-Depth.aspx).

Aero Wizards

Windows Vista introduces a new, modern look for the common wizard. As with the older wizard standard, AeroTM wizards are just a variation on the property sheet API. This makes it quite simple to update your applications to take advantage of the new user interface. WTL makes it even easier by providing a set of class templates that wrap all of the new functionality for you.

Simply derive your wizard window class from the CAeroWizardFrameImpl class template and it will take care of applying the appropriate flags to instruct Windows to use the new Aero user interface. CAeroWizardFrameImpl derives from CPropertySheetImpl, which provides all of the common functionality. The wizard pages simply derive from the CAeroWizardPageImpl class, which provides methods to control the new Aero wizard buttons. Figure 6 provides a simple yet complete example of an Aero wizard with a single page.

Figure 6 Simple Aero Wizard

class SamplePage : public CAeroWizardPageImpl<SamplePage> { public: BEGIN_MSG_MAP(SamplePage) MESSAGE_HANDLER_EX(WM_INITDIALOG, OnInitDialog) CHAIN_MSG_MAP(__super) END_MSG_MAP() enum { IDD = IDD_SAMPLE_PAGE }; SamplePage() { VERIFY(m_title.LoadString(IDS_PAGE_TITLE)); SetHeaderTitle(m_title); } private: LRESULT OnInitDialog(UINT /*message*/, WPARAM /*wParam*/, LPARAM /*lParam*/) { ShowWizardButtons( PSWIZB_BACK | PSWIZB_NEXT | PSWIZB_FINISH | PSWIZB_CANCEL, PSWIZB_BACK | PSWIZB_NEXT | PSWIZB_CANCEL); EnableWizardButtons(PSWIZB_BACK, 0); SetButtonText(PSWIZB_NEXT, L"&Run"); return TRUE; } CString m_title; }; class SampleWizard : public CAeroWizardFrameImpl<SampleWizard> { public: BEGIN_MSG_MAP(SampleWizard) CHAIN_MSG_MAP(__super) END_MSG_MAP() SampleWizard() : CAeroWizardFrameImpl<SampleWizard>(IDS_WIZARD_TITLE) { VERIFY(AddPage(m_page)); } private: SamplePage m_page; };

Figure 6 Simple Aero Wizard

Figure 6** Simple Aero Wizard **

As you can see, there isn't much new to talk about and moving to Aero wizards is pretty painless. The main task will be to manage the standard buttons that adorn an Aero wizard. The example demonstrates the ShowWizardButtons, EnableWizardButtons, and SetButtonText methods. ShowWizardButtons shows or hides one or more buttons. The first parameter indicates the buttons to operate on and the second parameter indicates which of those buttons to show. Similarly, the first parameter of EnableWizardButtons indicates the buttons to operate on and the second parameter indicates which of those buttons to enable. The rest will be disabled. Finally, SetButtonText updates the text captions for the Next, Finish, and Cancel buttons.

New File Dialogs

The new file dialogs provided by Windows Vista present a fresh appearance consistent with the new Windows Explorer user interface and are a welcome replacement for the aging GetOpenFileName and GetSaveFileName functions that have been in use for years. The new file dialogs are exposed through a set of COM interfaces so they are not particularly hard to use directly. However, it can get tedious to write the same boilerplate COM code every time you need to pop up a file dialog, especially if you need to respond to the various file dialog events that require you to provide COM interface implementations. Fortunately, WTL does a good job of wrapping it all up in an easy-to-use set of classes. Figure 7 provides an example of a file open dialog implementation.

Figure 7 File Open Dialog

class SampleOpenDialog : public CShellFileOpenDialogImpl<SampleOpenDialog> { public: SampleOpenDialog() { COMDLG_FILTERSPEC fileTypes[] = { { L"Text Documents", L"*.txt" }, { L"All Files", L"*.*" } }; COM_VERIFY(GetPtr()->SetFileTypes(_countof(fileTypes), fileTypes)); } };

Figure 7 File Open Dialog

Figure 7** File Open Dialog **(Click the image for a larger view)

The COMDLG_FILTERSPEC structure provides a much more manageable format for specifying the file types compared to the multi-string approach used by GetOpenFileName. An array of these structures is then passed to the SetFileTypes method to specify the file types the dialog will provide filtering for. The GetPtr method returns the dialog's IFileOpenDialog interface pointer used to customize the dialog appearance and behavior.

The new file dialogs also let you respond to a variety of events. However, you need to implement the IFileDialogEvents interface to receive these events. Fortunately, WTL implements this for you via the CShellFileDialogImpl base class template so you only need to provide the handlers for the events you are interested in.

Figure 8 presents a list of overrides that the CShellFileDialogImpl class provides. You might, for example, want to validate that the selected file is acceptable before closing the file open dialog. This is easily achieved by the OnFileOk event called just before the dialog is about to return the result, by preventing the dialog from closing should you decide that the selected file is not acceptable. The example in Figure 9 illustrates this technique.

Figure 9 Handling an OnFileOk Event

class SampleOpenDialog : public CShellFileOpenDialogImpl<SampleOpenDialog> { public: // ... HRESULT OnFileOk() { CString filePath; COM_VERIFY(GetFilePath(filePath)); // Validate file here... bool acceptable = ... return acceptable ? S_OK : S_FALSE; } };

Figure 8 CShellFileDialogImpl Overrides

Method Description
HRESULT OnFileOk() User made a selection.
HRESULT OnFolderChanging(IShellItem*) Enables preventing folder navigations.
HRESULT OnFolderChange() Called after OnFolderChanging indicating user navigated to new folder.
HRESULT OnSelectionChange() User changes selection in dialog's view.
HRESULT OnShareViolation(IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) Enables handling of sharing violations.
HRESULT OnOverwrite(IShellItem*, FDE_OVERWRITE_RESPONSE*) User chose to overwrite a file.

Finally, the file open dialog can be used as follows:

SampleOpenDialog dialog; if (IDOK == dialog.DoModal()) { CString filePath; COM_VERIFY(dialog.GetFilePath(filePath)); // Use file here... }

The new file dialogs provide many more features through the various COM interfaces. For a detailed look at the capabilities offered by the new file dialogs API, read my blog post "Windows Vista for Developers–Part 6–The New File Dialogs" (weblogs.asp.net/kennykerr/archive/2006/11/10/Windows-Vista-for-Developers-_1320_-Part-6-_1320_-The-New-File-Dialogs.aspx).

Other WTL Features to Explore

In a previous Windows with C++ column, I introduced many of the control enhancements in Windows Vista (msdn.microsoft.com/msdnmag/issues/07/08/WindowsCPP). WTL now wraps most all of the new features with updated classes and macros for handling the new messages and notifications. There is even more to love about the latest version of WTL, from improvements to the resizable dialog support, a new tab view class as well as many other helpful additions and improvements. Take a few minutes to explore the WTL headers. If you're new to WTL you will be pleasantly surprised at the powerful classes and minimalist design of this excellent C++ library for Windows.

Send your questions and comments for Kenny to mmwincpp@microsoft.com.

Kenny Kerr is a software craftsman specializing in software development for Windows. He has a passion for writing and teaching developers about programming and software design. Reach Kenny at weblogs.asp.net/kennykerr.