WTL for MFC Programmers, Part IX - GDI Classes, Common Dialogs, and Utility Classes
Contents
- Introduction
- GDI Wrapper Classes
- Resource-Loading Functions
- Using Common Dialogs
- Other useful classes and global functions
- The Sample Project
- Copyright and license
- Revision History
Introduction
WTL contains many wrappers and utility classes that haven't gotten full coverage yet in this series, such as CString
and CDC
. WTL has a nice system for wrapping GDI objects, some useful functions for loading resources, and classes that make it easier to use some of the Win32 common dialogs. Here in Part IX, I'll cover some of the most commonly-used utility classes.
This article discusses four categories of features:
- GDI wrapper classes
- Resource-loading functions
- Using the file-open and choose-folder common dialogs
- Other useful classes and global functions
GDI Wrapper Classes
WTL takes a rather different approach to its GDI wrappers than MFC. WTL's approach is to have one template class for each type of GDI object, each template having one bool
parameter called t_bManaged
. This parameter controls whether an instance of that class "manages" (or owns) the wrapped GDI object. If t_bManaged
is false
, the C++ object does not manage the lifetime of the GDI object; the C++ object is a simple wrapper around the GDI object handle. If t_bManaged
is true
, two things change:
- The destructor calls
DeleteObject()
on the wrapped handle, if it is not NULL. Attach()
callsDeleteObject()
on the wrapped handle, if it is not NULL, before attaching the C++ object to the new handle.
This design is in line with the ATL window classes, where CWindow
is a plain wrapper around an HWND
, and CWindowImpl
manages the lifetime of a window.
The GDI wrapper classes are defined in atlgdi.h, with the exception of CMenuT
, which is in atluser.h. You don't have to include these headers yourself, because atlapp.h always includes them for you. Each class also has typedefs with easier-to-remember names:
Wrapped GDI object |
Template class |
Typedef for managed object |
Typedef for plain wrapper |
---|---|---|---|
Pen |
|
|
|
Brush |
|
|
|
Font |
|
|
|
Bitmap |
|
|
|
Palette |
|
|
|
Region |
|
|
|
Device context |
|
|
|
Menu |
|
|
|
I like this approach, compared to MFC which passes around pointers to objects. You never have to worry about getting a NULL pointer (the wrapped handle might be NULL, but that's another matter), nor do you have any special cases where you get a temporary object that you can't hang on to for more than one function call. It is also very cheap to create an instance of any of these classes since they only have one member variable, the handle being wrapped. As is the case with CWindow
, there is no problem passing a wrapper class object between threads, since WTL keeps no thread-specific maps like MFC.
There are additional device context wrapper classes for use in special drawing scenarios:
CClientDC
: Wraps calls toGetDC()
andReleaseDC()
, used to draw in a window's client areaCWindowDC
: Wraps calls toGetWindowDC()
andReleaseDC()
, used to draw anywhere in a window.CPaintDC
: Wraps calls toBeginPaint()
andEndPaint()
, used in aWM_PAINT
handler.
Each of these classes takes a HWND
in the constructor, and behaves like the MFC classes of the same name. All three are derived from CDC
, so these classes all manage their device contexts.
Common functions in the wrapper classes
The GDI wrapper classes follow the same design. To be concise, I'll cover the methods in CBitmapT
here, but the other classes work similarly.
- The wrapped GDI object handle
- Each class keeps one public member variable that holds the GDI object handle that the C++ object is associated with.
CBitmapT
has anHBITMAP
member calledm_hBitmap
. - Constructor
- The constructor has one parameter, an
HBITMAP
, which defaults to NULL.m_hBitmap
is initialized to this value. - Destructor
- If
t_bManaged
is true, andm_hBitmap
is not NULL, then the destructor callsDeleteObject()
to destroy the bitmap. Attach()
andoperator =
- These methods both take an
HBITMAP
handle. Ift_bManaged
istrue
, andm_hBitmap
is not NULL, these methods callDeleteObject()
to destroy the bitmap that theCBitmapT
object is managing. Then they setm_hBitmap
to theHBITMAP
that was passed in as the parameter. Detach()
Detach()
setsm_hBitmap
to NULL, then returns the value that was inm_hBitmap
. AfterDetach()
returns, theCBitmapT
object is no longer associated with a GDI bitmap.- Methods for creating a GDI object
CBitmapT
has wrappers for the Win32 APIs that create a bitmap:LoadBitmap()
,LoadMappedBitmap()
,CreateBitmap()
,CreateBitmapIndirect()
,CreateCompatibleBitmap()
,CreateDiscardableBitmap()
,CreateDIBitmap()
, andCreateDIBSection()
. These methods will assert ifm_hBitmap
is not NULL; to reuse aCBitmapT
object for a different GDI bitmap, callDeleteObject()
orDetach()
first.DeleteObject()
DeleteObject()
destroys the GDI bitmap object, then setsm_hBitmap
to NULL. This method should be called only ifm_hBitmap
is not NULL; it will assert otherwise.IsNull()
IsNull()
returnstrue
ifm_hBitmap
is NULL, orfalse
otherwise. Use this method to test whether theCBitmapT
object is currently associated with a GDI bitmap.operator HBITMAP
- This converter returns
m_hBitmap
, and lets you pass aCBitmapT
object to a function or Win32 API that takes anHBITMAP
handle. This converter is also called when aCBitmapT
is evaluated in a boolean context, and evaluates to the logical opposite ofIsNull()
. Therefore, these two if statements are equivalent:CBitmapHandle bmp = /* some HBITMAP value */; if ( !bmp.IsNull() ) { do something... } if ( bmp ) { do something more... }
GetObject()
wrappersCBitmapT
has a type-safe wrapper for the Win32 APIGetObject()
:GetBitmap()
. There are two overloads: one that takes aLOGBITMAP*
and calls straight through toGetObject()
; and one that takes aLOGBITMAP&
and returns abool
indicating success. The latter version is the easier one to use. For example:CBitmapHandle bmp2 = /* some HBITMAP value */; LOGBITMAP logbmp = {0}; bool bSuccess; if ( bmp2 ) bSuccess = bmp2.GetLogBitmap ( logbmp );
- Wrappers for APIs that operate on the GDI object
CBitmapT
has wrappers for Win32 APIs that take anHBITMAP
parameter:GetBitmapBits()
,SetBitmapBits()
,GetBitmapDimension()
,SetBitmapDimension()
,GetDIBits()
, andSetDIBits()
. These methods will assert ifm_hBitmap
is NULL.- Other utility methods
CBitmapT
has two useful methods that operate onm_hBitmap
:LoadOEMBitmap()
andGetSize()
.
Using CDCT
CDCT
is a bit different from the other classes, so I'll cover the differences separately.
Differences in methods
The method to destroy a DC is called DeleteDC()
instead of DeleteObject()
.
Selecting objects into a DC
One aspect of MFC's CDC
that is prone to errors is selecting objects into a DC. MFC's CDC
has several overloaded SelectObject()
functions that each take a pointer to a different kind of GDI wrapper class (CPen*
, CBitmap*
, and so on). If you pass a C++ object to SelectObject()
, instead of a pointer to a C++ object, the code ends up calling the undocumented overload that accepts an HGDIOBJ
handle, and this is what causes the problems.
WTL's CDCT
takes a better approach, and has several select methods, each of which works with just one type of GDI object:
HPEN SelectPen(HPEN hPen) HBRUSH SelectBrush(HBRUSH hBrush) HFONT SelectFont(HFONT hFont) HBITMAP SelectBitmap(HBITMAP hBitmap) int SelectRgn(HRGN hRgn) HPALETTE SelectPalette(HPALETTE hPalette, BOOL bForceBackground)
In debug builds, each method asserts that m_hDC
is not NULL, and that the parameter is a handle to the correct type of GDI object. They then call the SelectObject()
API and cast the SelectObject()
return value to the appropriate type.
There are also helper methods that call GetStockObject()
with a given constant, and then select the object into the DC:
HPEN SelectStockPen(int nPen) HBRUSH SelectStockBrush(int nBrush) HFONT SelectStockFont(int nFont) HPALETTE SelectStockPalette(int nPalette, BOOL bForceBackground)
Differences from the MFC wrapper classes
Fewer constructors: The wrappers classes lack constructors that create a new GDI object. For example, MFC's CBrush
has constructors that create a solid or patterned brush. With the WTL classes, you must use a method to create the GDI object.
Selecting objects into a DC is done better: See the Using CDCT section above.
No m_hAttribDC
: WTL's CDCT
does not have a m_hAttribDC
member.
Minor parameter differences in some methods: For example, CDC::GetWindowExt()
returns a CSize
object in MFC; while in WTL the method returns a bool
, and the size is returned via an output parameter.
Resource-Loading Functions
WTL has several global functions that are helpful shortcuts for loading various types of resources. We'll need to know about one utility class before getting on to the functions: _U_STRINGorID
.
In Win32, most types of resources can be identified by a string (LPCTSTR
) or an unsigned integer (UINT
). APIs that take a resource identifier take an LPCTSTR
parameter, and if you want to pass a UINT
, you need to use the MAKEINTRESOURCE
macro to convert it to an LPCTSTR
. _U_STRINGorID
, when used as the type of a resource identifier parameter, hides this distinction so that the caller can pass either a UINT
or LPCTSTR
directly. The function can then use a CString
to load the string if necessary:
void somefunc ( _U_STRINGorID id ) { CString str ( id.m_lpstr ); // use str... } void func2() { // Call 1 - using a string literal somefunc ( _T("Willow Rosenberg") ); // Call 2 - using a string resource ID somefunc ( IDS_BUFFY_SUMMERS ); }
This works because the CString
constructor that takes an LPCTSTR
checks whether the parameter is actually a string ID. If so, the string is loaded from the string table and assigned to the CString
.
In VC 6, _U_STRINGorID
is provided by WTL in atlwinx.h. In VC 7, _U_STRINGorID
is part of ATL. Either way, the class definition will always be included for you by other ATL/WTL headers.
The functions in this section load a resource from the resource instance handle kept in the _Module
global variable (in VC 6) or the _AtlBaseModule
global (in VC 7). Using other modules for resources is beyond the scope of this article, so I will not be covering it here. Just remember that by default, the functions look in the EXE or DLL that the code is running in. The functions do nothing more than call through to APIs, their utility is in the simplified resource identifier handling provided by _U_STRINGorID
.
HACCEL AtlLoadAccelerators(_U_STRINGorID table)Calls through to
LoadAccelerators()
.HMENU AtlLoadMenu(_U_STRINGorID menu)Calls through to
LoadMenu()
.HBITMAP AtlLoadBitmap(_U_STRINGorID bitmap)Calls through to
LoadBitmap()
.HCURSOR AtlLoadCursor(_U_STRINGorID cursor)Calls through to
LoadCursor()
.HICON AtlLoadIcon(_U_STRINGorID icon)Calls through to
LoadIcon()
. Note that this function - likeLoadIcon()
- can only load 32x32 icons.int AtlLoadString(UINT uID, LPTSTR lpBuffer, int nBufferMax) bool AtlLoadString(UINT uID, BSTR& bstrText)Call through to
LoadString()
. The string can be returned in either aTCHAR
buffer, or assigned to aBSTR
, depending on which overload you use. Note that these functions only accept aUINT
as the resource ID, because string table entries cannot have string identifiers.
This group of functions wrap calls to LoadImage()
, and take additional parameters that are passed on to LoadImage()
.
HBITMAP AtlLoadBitmapImage( _U_STRINGorID bitmap, UINT fuLoad = LR_DEFAULTCOLOR)Calls
LoadImage()
with theIMAGE_BITMAP
type, passing along thefuLoad
flags.HCURSOR AtlLoadCursorImage( _U_STRINGorID cursor, UINT fuLoad = LR_DEFAULTCOLOR | LR_DEFAULTSIZE, int cxDesired = 0, int cyDesired = 0)Calls
LoadImage()
with theIMAGE_CURSOR
type, passing along thefuLoad
flags. Since one cursor resource can contain several different-sized cursors, you can pass dimensions for thecxDesired
andcyDesired
parameters to load a cursor with a particular size.HICON AtlLoadIconImage( _U_STRINGorID icon, UINT fuLoad = LR_DEFAULTCOLOR | LR_DEFAULTSIZE, int cxDesired = 0, int cyDesired = 0)Calls
LoadImage()
with theIMAGE_ICON
type, passing along thefuLoad
flags. ThecxDesired
andcyDesired
parameters are used as inAtlLoadCursorImage()
.
This group of functions wrap calls to load system-defined resources (for example, the standard hand cursor). Some of these resource IDs (mostly the ones for bitmaps) are not included by default; you need to #define
the OEMRESOURCE
symbol in your stdafx.h in order to reference them.
HBITMAP AtlLoadSysBitmap(LPCTSTR lpBitmapName)Calls
LoadBitmap()
with a NULL resource handle. Use this function to load any of theOBM_*
bitmaps listed in theLoadBitmap()
documentation.HCURSOR AtlLoadSysCursor(LPCTSTR lpCursorName)Calls
LoadCursor()
with a NULL resource handle. Use this function to load any of theIDC_*
cursors listed in theLoadCursor()
documentation.HICON AtlLoadSysIcon(LPCTSTR lpIconName)Calls
LoadIcon()
with a NULL resource handle. Use this function to load any of theIDI_*
icons listed in theLoadIcon()
documentation. Note that this function - likeLoadIcon()
- can only load 32x32 icons.HBITMAP AtlLoadSysBitmapImage( WORD wBitmapID, UINT fuLoad = LR_DEFAULTCOLOR)Calls
LoadImage()
with a NULL resource handle and theIMAGE_BITMAP
type. You can use this function to load the same bitmaps asAtlLoadSysBitmap()
.HCURSOR AtlLoadSysCursorImage( _U_STRINGorID cursor, UINT fuLoad = LR_DEFAULTCOLOR | LR_DEFAULTSIZE, int cxDesired = 0, int cyDesired = 0)Calls
LoadImage()
with a NULL resource handle and theIMAGE_CURSOR
type. You can use this function to load the same cursors asAtlLoadSysCursor()
.HICON AtlLoadSysIconImage( _U_STRINGorID icon, UINT fuLoad = LR_DEFAULTCOLOR | LR_DEFAULTSIZE, int cxDesired = 0, int cyDesired = 0)Calls
LoadImage()
with a NULL resource handle and theIMAGE_ICON
type. You can use this function to load the same icons asAtlLoadSysIcon()
, but you can also specify a different size such as 16x16.
Finally, this group of functions are type-safe wrappers for the GetStockObject()
API.
HPEN AtlGetStockPen(int nPen) HBRUSH AtlGetStockBrush(int nBrush) HFONT AtlGetStockFont(int nFont) HPALETTE AtlGetStockPalette(int nPalette)
Each function checks that you're passing in a sensible value (e.g., AtlGetStockPen()
only accepts WHITE_PEN
, BLACK_PEN
, and so on), then calls through to GetStockObject()
.
Using Common Dialogs
WTL has classes that make using the Win32 common dialogs easier. Each class handles messages and callbacks that the common dialog sends, and in turn calls overridable functions. This is the same design used in property sheets, where you write handlers for individual property sheet notifications (e.g., OnWizardNext()
for handling PSN_WIZNEXT
) that are called by CPropertyPageImpl
when necessary.
WTL contains two classes for each common dialog; for example, the Choose Folder dialog is wrapped by CFolderDialogImpl
and CFolderDialog
. If you need to change any defaults or write handlers for any messages, you derive a new class from CFolderDialogImpl
and make the changes in that class. If the default behavior of CFolderDialogImpl
is sufficient, you can use CFolderDialog
.
The common dialogs and their corresponding WTL classes are:
Common dialog |
Corresponding Win32 API |
Implementation class |
Non-customizable class |
---|---|---|---|
File Open and File Save |
|
|
|
Choose Folder |
|
|
|
Choose Font |
|
|
|
Choose Color |
|
|
|
Printing and Print Setup |
|
|
|
Printing (Windows 2000 and later) |
|
|
|
Page Setup |
|
|
|
Text find and replace |
|
|
|
Since writing about all those classes would make this article far too long, I'll cover just the first two, which are the ones you'll likely use most often.
CFileDialog
CFileDialog
, and its base CFileDialogImpl
, are used to show File Open and File Save dialogs. The two most important data members in CFileDialogImpl
are m_ofn
and m_szFileName
. m_ofn
is an OPENFILENAME
that CFileDialogImpl
sets up for you with some meaningful default values; just as in MFC, you can change the data in this struct directly if necessary. m_szFileName
is a TCHAR
array that holds the name of the selected file. (Since CFileDialogImpl
only has this one string for holding a filename, you'll need to provide your own buffer when you use a multiple-select open file dialog.)
The basic steps in using a CFileDialog
are:
- Construct a
CFileDialog
object, passing any initial data to the constructor. - Call
DoModal()
. - If
DoModal()
returnsIDOK
, get the selected file fromm_szFileName
.
Here is the CFileDialog
constructor:
CFileDialog::CFileDialog ( BOOL bOpenFileDialog, LPCTSTR lpszDefExt = NULL, LPCTSTR lpszFileName = NULL, DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, LPCTSTR lpszFilter = NULL, HWND hWndParent = NULL )
bOpenFileDialog
should be true
to create a File-Open dialog (CFileDialog
will call GetOpenFileName()
to show the dialog), or false
to create a File-Save dialog (CFileDialog
will call GetSaveFileName()
). The remaining parameters are stored directly in the appropriate members of the m_ofn
struct, but they are optional since you can access m_ofn
directly before calling DoModal()
.
A significant difference between MFC's CFileDialog
is that the lpszFilter
parameter must be a null-character-delimited string list (that is, the format documented in the OPENFILENAME
docs), instead of a pipe-separated list.
Here is an example of using a CFileDialog
with a filter that selects Word 12 files (*.docx
):
CString sSelectedFile; CFileDialog fileDlg ( true, _T("docx"), NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, _T("Word 12 Files\0*.docx\0All Files\0*.*\0") ); if ( IDOK == fileDlg.DoModal() ) sSelectedFile = fileDlg.m_szFileName;
CFileDialog
isn't very localization-friendly, since the constructor uses LPCTSTR
parameters. That filter string is also a bit hard to read at first glance. There are two solutions, either set up m_ofn
before calling DoModal()
, or derive a new class from CFileDialogImpl
that has the improvements we want. We'll take the second approach here, and make a new class that has the following changes:
- The string parameters in the constructor are
_U_STRINGorID
instead ofLPCTSTR
. - The filter string can use pipes to separate the fields, as in MFC, instead of null characters.
- The dialog will be automatically centered relative to its parent window.
We'll start by writing a class whose constructor takes parameters similar to the CFileDialogImpl
constructor:
class CMyFileDialog : public CFileDialogImpl<CMyFileDialog> { public: // Construction CMyFileDialog ( BOOL bOpenFileDialog, _U_STRINGorID szDefExt = 0U, _U_STRINGorID szFileName = 0U, DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _U_STRINGorID szFilter = 0U, HWND hwndParent = NULL ); protected: LPCTSTR PrepFilterString ( CString& sFilter ); CString m_sDefExt, m_sFileName, m_sFilter; };
The constructor initializes the three CString
members, loading strings if necessary:
CMyFileDialog::CMyFileDialog ( BOOL bOpenFileDialog, _U_STRINGorID szDefExt, _U_STRINGorID szFileName, DWORD dwFlags, _U_STRINGorID szFilter, HWND hwndParent ) : CFileDialogImpl<CMyFileDialog>(bOpenFileDialog, NULL, NULL, dwFlags, NULL, hwndParent), m_sDefExt(szDefExt.m_lpstr), m_sFileName(szFileName.m_lpstr), m_sFilter(szFilter.m_lpstr) { }
Note that the string parameters are all NULL in the call to the base class constructor. This is because the base class constructor is always called before member initializers. To set up the string data in m_ofn
, we add some code that duplicates the initialization steps that the CFileDialogImpl
constructor would do:
CMyFileDialog::CMyFileDialog(...) { m_ofn.lpstrDefExt = m_sDefExt; m_ofn.lpstrFilter = PrepFilterString ( m_sFilter ); // setup initial file name if ( !m_sFileName.IsEmpty() ) lstrcpyn ( m_szFileName, m_sFileName, _MAX_PATH ); }
PrepFilterString()
is a helper method that takes a pipe-delimited filter string, changes the pipes to null characters, and returns a pointer to the beginning of the string. The result is a string list that's in the proper format for use in an OPENFILENAME
.
LPCTSTR CMyFileDialog::PrepFilterString(CString& sFilter) { LPTSTR psz = sFilter.GetBuffer(0); LPCTSTR pszRet = psz; while ( '\0' != *psz ) { if ( '|' == *psz ) *psz++ = '\0'; else psz = CharNext ( psz ); } return pszRet; }
Those changes make the string-handling easier. To implement automatic centering, we'll override the OnInitDone()
notification. This requires us to add a message map (so we can chain notification messages to the base class), and our OnInitDone()
handler:
class CMyFileDialog : public CFileDialogImpl<CMyFileDialog> { public: // Construction CMyFileDialog(...); // Maps BEGIN_MSG_MAP(CMyFileDialog) CHAIN_MSG_MAP(CFileDialogImpl<CMyFileDialog>) END_MSG_MAP() // Overrides void OnInitDone ( LPOFNOTIFY lpon ) { GetFileDialogWindow().CenterWindow(lpon->lpOFN->hwndOwner); } protected: LPCTSTR PrepFilterString ( CString& sFilter ); CString m_sDefExt, m_sFileName, m_sFilter; };
The window attached to the CMyFileDialog
object is actually a child of the File Open dialog. Since we need the top-most window in the stack, we call GetFileDialogWindow()
to get that window.
CFolderDialog
CFolderDialog
, and its base CFolderDialogImpl
, are used to show a Browse For Folder dialog. While the dialog supports browsing anywhere within the shell namespace, CFolderDialog
is only capable of browsing within the file system. The two most important data members in CFolderDialogImpl
are m_bi
and m_szFolderPath
. m_bi
is an BROWSEINFO
that CFolderDialogImpl
manages and passes to the SHBrowseForFolder()
API; you can change the data in this struct directly if necessary. m_szFolderPath
is a TCHAR
array that holds the name of the selected folder.
The basic steps in using a CFolderDialog
are:
- Construct a
CFolderDialog
object, passing any initial data to the constructor. - Call
DoModal()
. - If
DoModal()
returnsIDOK
, get the path to the selected folder fromm_szFolderPath
.
Here is the CFolderDialog
constructor:
CFolderDialog::CFolderDialog ( HWND hWndParent = NULL, LPCTSTR lpstrTitle = NULL, UINT uFlags = BIF_RETURNONLYFSDIRS )
hWndParent
is the owner window for the browse dialog. You can either set it here in the constructor, or in the DoModal()
call. lpstrTitle
is a string that will be shown above the tree control in the dialog. uFlags
are flags that control the dialog's behavior, and should always include BIF_RETURNONLYFSDIRS
so the tree only shows file system directories. Other values for uFlags
that you can use are listed in the docs for BROWSEINFO
, but remember that some flags may not produce good results, such as BIF_BROWSEFORPRINTER
. UI-related flags like BIF_USENEWUI
will work fine. Note that the lpstrTitle
parameter has the same usability problems as the strings in the CFileDialog
constructor.
Here is an example of selecting a directory using CFolderDialog
:
CString sSelectedDir; CFolderDialog fldDlg ( NULL, _T("Select a dir"), BIF_RETURNONLYFSDIRS|BIF_NEWDIALOGSTYLE ); if ( IDOK == fldDlg.DoModal() ) sSelectedDir = fldDlg.m_szFolderPath;
To demonstrate customizing CFolderDialog
, we'll derive a class from CFolderDialogImpl
and set the initial selection. This dialog's callbacks don't use window messages, so the class doesn't need a message map. Instead, we override the OnInitialized()
method, which gets called when the base class receives the BFFM_INITIALIZED
notification. OnInitialized()
calls CFolderDialogImpl::SetSelection()
to change the selection in the dialog.
class CMyFolderDialog : public CFolderDialogImpl<CMyFolderDialog> { public: // Construction CMyFolderDialog ( HWND hWndParent = NULL, _U_STRINGorID szTitle = 0U, UINT uFlags = BIF_RETURNONLYFSDIRS ) : CFolderDialogImpl<CMyFolderDialog>(hWndParent, NULL, uFlags), m_sTitle(szTitle.m_lpstr) { m_bi.lpszTitle = m_sTitle; } // Overrides void OnInitialized() { // Set the initial selection to the Windows dir. TCHAR szWinDir[MAX_PATH]; GetWindowsDirectory ( szWinDir, MAX_PATH ); SetSelection ( szWinDir ); } protected: CString m_sTitle; };
Other useful classes and global functions
Struct wrappers
WTL has the classes CSize
, CPoint
, and CRect
, that wrap the SIZE
, POINT
, and RECT
structs respectively. They work like their MFC counterparts.
Classes for handling dual-typed arguments
As mentioned earlier, you can use the _U_STRINGorID
type for a function parameter that can be a numeric or string resource ID. There are two other classes that work similarly:
_U_MENUorID
: This type can be constructed from aUINT
orHMENU
, and is meant to be used inCreateWindow()
wrappers. ThehMenu
parameter toCreateWindow()
is actually a window ID when the window being created is a child window, so_U_MENUorID
hides the distinction between the two usages._U_MENUorID
has one memberm_hMenu
, which can be passed as thehMenu
parameter toCreateWindow()
orCreateWindowEx()
._U_RECT
: This type can be constructed from aLPRECT
orRECT&
, and lets the caller pass in aRECT
struct, pointer to aRECT
, or a wrapper class likeCRect
that provides a converter toRECT
.
As with _U_STRINGorID
, _U_MENUorID
and _U_RECT
are always included for you by other headers.
Other utility classes
CString
WTL's CString
works just like MFC's CString
, so I won't be covering it in detail here. WTL's CString
has many extra methods that are used when you build with _ATL_MIN_CRT
defined. These methods, like _cstrchr()
, _cstrstr()
, are replacements for the corresponding CRT functions, which aren't available when _ATL_MIN_CRT
is defined.
CFindFile
CFindFile
wraps the FindFirstFile()
and FindNextFile()
APIs, and is a bit easier to use than MFC's CFileFind
. The general pattern of usage goes like this:
CFindFile finder; CString sPattern = _T("C:\\windows\\*.exe"); if ( finder.FindFirstFile ( sPattern ) ) { do { // act on the file that was found } while ( finder.FindNextFile() ); } finder.Close();
If FindFirstFile()
returns true
, at least one file matched the pattern. Inside the do
loop, you can access the public CFindFile
member m_fd
, which is a WIN32_FIND_DATA
struct that holds the info about the file that was found. The loop continues until FindNextFile()
returns false
, indicating that all files have been enumerated.
CFindFile
has methods that return the data from m_fd
in easier-to-use forms. These methods return meaningful values only after a successful call to FindFirstFile()
or FindNextFile()
.
ULONGLONG GetFileSize()
Returns the file size as a 64-bit unsigned integer.
BOOL GetFileName(LPTSTR lpstrFileName, int cchLength) CString GetFileName()
Returns the filename and extension of the file that was found (copied from m_fd.cFileName
).
BOOL GetFilePath(LPTSTR lpstrFilePath, int cchLength) CString GetFilePath()
Returns the full path to the file that was found.
BOOL GetFileTitle(LPTSTR lpstrFileTitle, int cchLength) CString GetFileTitle()
Returns just the file title (that is, the filename with no extension) of the file that was found.
BOOL GetFileURL(LPTSTR lpstrFileURL, int cchLength) CString GetFileURL()
Creates a file://
URL that contains the full path to the file.
BOOL GetRoot(LPTSTR lpstrRoot, int cchLength) CString GetRoot()
Returns the directory that contains the file.
BOOL GetLastWriteTime(FILETIME* pTimeStamp) BOOL GetLastAccessTime(FILETIME* pTimeStamp) BOOL GetCreationTime(FILETIME* pTimeStamp)
These methods return the ftLastWriteTime
, ftLastAccessTime
, and ftCreationTime
members from m_fd
respectively.
CFindFile
also has some helper methods for checking the attributes of the file that was found.
BOOL IsDots()
Returns true
if the found file is the ".
" or "..
" directory.
BOOL MatchesMask(DWORD dwMask)
Compares the bits in dwMask
(which should be the FILE_ATTRIBUTE_*
constants) with the attributes of the file that was found. Returns true
if all the bits that are on in dwMask
are also on in the file's attributes.
BOOL IsReadOnly() BOOL IsDirectory() BOOL IsCompressed() BOOL IsSystem() BOOL IsHidden() BOOL IsTemporary() BOOL IsNormal() BOOL IsArchived()
These methods are shortcuts that call MatchesMask()
with a particular FILE_ATTRIBUTE_*
bit. For example, IsReadOnly()
calls MatchesMask(FILE_ATTRIBUTE_READONLY)
.
Global functions
WTL has several useful global functions that you can use to do things like DLL version checks and show message boxes.
bool AtlIsOldWindows()
Returns true if the operating system is Windows 95, 98, NT 3, or NT 4.
HFONT AtlGetDefaultGuiFont()
Returns the value of GetStockObject(DEFAULT_GUI_FONT)
. In English Windows 2000 and later (and other single-byte languages that use the Latin alphabet), this font's face name is "MS Shell Dlg". This is usable as a dialog box font, but not the best choice if you are creating your own fonts for use in your UI. MS Shell Dlg is an alias for MS Sans Serif, instead of the new UI font, Tahoma. To avoid getting MS Sans Serif, you can get the font used for message boxes with this code:
NONCLIENTMETRICS ncm = { sizeof(NONCLIENTMETRICS) }; CFont font; if ( SystemParametersInfo ( SPI_GETNONCLIENTMETRICS, 0, &ncm, false ) ) font.CreateFontIndirect ( &ncm.lfMessageFont );
An alternative is to check the face name of the font returned by AtlGetDefaultGuiFont()
. If the name is "MS Shell Dlg", you can change it to "MS Shell Dlg 2", an alias that resolves to Tahoma.
HFONT AtlCreateBoldFont(HFONT hFont = NULL)
Creates a bold version of a given font. If hFont
is NULL, AtlCreateBoldFont()
creates a bold version of the font returned by AtlGetDefaultGuiFont()
.
BOOL AtlInitCommonControls(DWORD dwFlags)
This is a wrapper for the InitCommonControlsEx()
API. It initializes an INITCOMMONCONTROLSEX
struct with the given flags, then calls the API.
HRESULT AtlGetDllVersion(HINSTANCE hInstDLL, DLLVERSIONINFO* pDllVersionInfo) HRESULT AtlGetDllVersion(LPCTSTR lpstrDllName, DLLVERSIONINFO* pDllVersionInfo)
These functions look in a given module for an exported function called DllGetVersion()
. If the function is found, it is called. If DllGetVersion()
is successful, it returns the version information in a DLLVERSIONINFO
struct.
HRESULT AtlGetCommCtrlVersion(LPDWORD pdwMajor, LPDWORD pdwMinor)
Returns the major and minor versions of comctl32.dll.
HRESULT AtlGetShellVersion(LPDWORD pdwMajor, LPDWORD pdwMinor)
Returns the major and minor versions of shell32.dll.
bool AtlCompactPath(LPTSTR lpstrOut, LPCTSTR lpstrIn, int cchLen)
Truncates a file path so it is less than cchLen
characters in length, adding an ellipsis at the end if the path is too long. This works similarly to the PathCompactPath()
and PathSetDlgItemPath()
functions in shlwapi.dll.
int AtlMessageBox(HWND hWndOwner, _U_STRINGorID message, _U_STRINGorID title = NULL, UINT uType = MB_OK | MB_ICONINFORMATION)
Displays a message box, like MessageBox()
, but uses _U_STRINGorID
parameters so you can pass string resource IDs. AtlMessageBox()
handles loading the strings if necessary.
Macros
There are various preprocessor macros that you'll see referenced in the WTL header files. Most of these macros can be set in the compiler settings to change behavior in the WTL code.
These macros are predefined or set by build settings, you'll see them referenced throughout the WTL code:
_WTL_VER
- Defined as
0x0710
for WTL 7.1. _ATL_MIN_CRT
- If defined, ATL does not link to the C runtime library. Since some WTL classes (notably
CString
) normally use CRT functions, special code is compiled that replaces the code that would normally be imported from the CRT. _ATL_VER
- Predefined as
0x0300
for VC 6,0x0700
for VC 7, and0x0800
for VC 8. _WIN32_WCE
- Defined if the current compilation is for a Windows CE binary. Some WTL code is disabled when the corresponding features are not available in CE.
The following macros are not defined by default. To use a macro, #define
it before all #include
statements in stdafx.h.
_ATL_NO_OLD_NAMES
- This macro is only useful if you are maintaining WTL 3 code. It adds some compiler directives to recognize two old class names:
CUpdateUIObject
becomesCIdleHandler
, andDoUpdate()
becomesOnIdle()
. _ATL_USE_CSTRING_FLOAT
- Define this symbol to enable floating-point support in
CString
;_ATL_MIN_CRT
must not also be defined. You need to define this symbol if you plan to use the%I64
prefix in a format string that you pass toCString::Format()
. Defining_ATL_USE_CSTRING_FLOAT
results inCString::Format()
calling_vstprintf()
, which understands the%I64
prefix. _ATL_USE_DDX_FLOAT
- Define this symbol to enable floating-point support in the DDX code;
_ATL_MIN_CRT
must not also be defined. _ATL_NO_MSIMG
- Define this symbol to prevent the compiler from seeing a
#pragma comment(lib, "msimg32")
line; also disables code inCDCT
that uses msimg32 functions:AlphaBlend()
,TransparentBlt()
,GradientFill()
. _ATL_NO_OPENGL
- Define this symbol to prevent the compiler from seeing a
#pragma comment(lib, "opengl32")
line; also disables code inCDCT
that uses OpenGL. _WTL_FORWARD_DECLARE_CSTRING
- Obsolete, use
_WTL_USE_CSTRING
instead. _WTL_USE_CSTRING
- Define this symbol to forward-declare
CString
. This way, code in headers that are normally included before atlmisc.h will be able to useCString
. _WTL_NO_CSTRING
- Define this symbol to prevent usage of
WTL::CString
. _WTL_NO_AUTOMATIC_NAMESPACE
- Define this symbol to prevent automatic execution of a
using namespace WTL
directive. _WTL_NO_AUTO_THEME
- Define this symbol to prevent
CMDICommandBarCtrlImpl
from using XP themes. _WTL_NEW_PAGE_NOTIFY_HANDLERS
- Define this symbol to use newer
PSN_*
notification handlers inCPropertyPage
. Since the old WTL 3 handlers are obsolete, this symbol should always be defined unless you are maintaining WTL 3 code that can't be updated. _WTL_NO_WTYPES
- Define this symbol to prevent the WTL versions of
CSize
,CPoint
, andCRect
from being defined. _WTL_NO_THEME_DELAYLOAD
- When building with VC 6, define this symbol to prevent uxtheme.dll from being automatically marked as a delay-load DLL.
NOTE: If neither _WTL_USE_CSTRING
nor _WTL_NO_CSTRING
is defined, then CString
can be used at any point after atlmisc.h is included.
The Sample Project
The demo project for this article is a downloader application called Kibbles that demonstrates the various classes that have been covered in this article. It uses the BITS (background intelligent transfer service) component that you can get for Windows 2000 and later; since this app only runs on NT-based OSes, I also made it a Unicode project.
The app has a view window that shows the download progress, using various GDI calls including Pie()
which draws the pie chart shapes. When the app is first run, you'll see the UI in its initial state:
You can drag a link from a browser into the window to create a new BITS job that will download the target of the link to your My Documents folder. You can also click the third toolbar button to add any URL that you want to the job. The fourth button lets you change the default download directory.
When a download job is in progress, Kibbles shows some details about the job, and shows the download progress like so:
The first two buttons in the toolbar let you change the colors used in the progress display. The first button opens an options dialog where you can set the colors used for various parts of the display:
The dialog uses the great button class from Tim Smith's article Color Picker for WTL with XP themes; check out the CChooseColorsDlg
class in the Kibbles project to see it in action. The Text color button is a regular button, and the OnChooseTextColor()
handler demonstrates how to use the WTL class CColorDialog
. The second toolbar button changes all the colors to random values.
The fifth button lets you set a background picture, which will be drawn in the part of the pie that shows how much has been downloaded. The default picture is included as a resource, but if you have any BMP files in your My Pictures directory, you can select one of those as well.
CMainFrame::OnToolbarDropdown()
contains the code that handles the button press event and shows a popup menu. That function also uses CFindFile
to enumerate the contents of the My Pictures directory. You can check out CKibblesView::OnPaint()
to see the code that does the various GDI operations that draw the UI.
An important note about the toolbar: The toolbar uses a 256-color bitmap, however the VC toolbar editor only works with 16-color bitmaps. If you ever edit the toolbar using the editor, VC will reduce the bitmap to 16 colors. What I suggest is keeping a high-color version of the bitmap in a separate directory, making changes to it directly using a graphics program, then saving a 256-color version in the res
directory.
Copyright and License
This article is copyrighted material, (c)2006 by Michael Dunn. 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.
With the exception of ColorButton.cpp and ColorButton.h, 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.
The files ColorButton.cpp and ColorButton.h come from Color Picker for WTL with XP themes by Tim Smith. They are not covered by the above license statement; see the comments in those files for their license.