Contents
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() calls DeleteObject() 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
|
CPenT
|
CPen
|
CPenHandle
|
|
Brush
|
CBrushT
|
CBrush
|
CBrushHandle
|
|
Font
|
CFontT
|
CFont
|
CFontHandle
|
|
Bitmap
|
CBitmapT
|
CBitmap
|
CBitmapHandle
|
|
Palette
|
CPaletteT
|
CPalette
|
CPaletteHandle
|
|
Region
|
CRgnT
|
CRgn
|
CRgnHandle
|
|
Device context
|
CDCT
|
CDC
|
CDCHandle
|
|
Menu
|
CMenuT
|
CMenu
|
CMenuHandle
|
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 to GetDC() and ReleaseDC(), used to draw in a
window's client area
CWindowDC: Wraps calls to GetWindowDC() and ReleaseDC(), used to draw
anywhere in a window.
CPaintDC: Wraps calls to BeginPaint() and EndPaint(), used in a WM_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 an HBITMAP member called m_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, and m_hBitmap is not NULL, then the destructor calls DeleteObject()
to destroy the bitmap.
Attach() and operator =
- These methods both take an
HBITMAP handle. If t_bManaged is true, and
m_hBitmap is not NULL, these methods call DeleteObject() to destroy the bitmap that the
CBitmapT object is managing. Then they set m_hBitmap to the HBITMAP that
was passed in as the parameter.
Detach()
Detach() sets m_hBitmap to NULL, then returns the value that was in m_hBitmap.
After Detach() returns, the CBitmapT 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(), and CreateDIBSection(). These methods will assert if m_hBitmap
is not NULL; to reuse a CBitmapT object for a different GDI bitmap, call DeleteObject()
or Detach() first.
DeleteObject()
DeleteObject() destroys the GDI bitmap object, then sets m_hBitmap to NULL. This
method should be called only if m_hBitmap is not NULL; it will assert otherwise.
IsNull()
IsNull() returns true if m_hBitmap is NULL, or false otherwise.
Use this method to test whether the CBitmapT object is currently associated with a GDI bitmap.
operator HBITMAP
- This converter returns
m_hBitmap, and lets you pass a CBitmapT object to a function
or Win32 API that takes an HBITMAP handle. This converter is also called when a CBitmapT
is evaluated in a boolean context, and evaluates to the logical opposite of IsNull(). Therefore, these
two if statements are equivalent:
CBitmapHandle bmp = ;
if ( !bmp.IsNull() ) { do something... }
if ( bmp ) { do something more... }
GetObject() wrappers
CBitmapT has a type-safe wrapper for the Win32 API GetObject(): GetBitmap().
There are two overloads: one that takes a LOGBITMAP* and calls straight through to GetObject();
and one that takes a LOGBITMAP& and returns a bool indicating success. The latter
version is the easier one to use. For example:
CBitmapHandle bmp2 = ;
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 an HBITMAP parameter: GetBitmapBits(),
SetBitmapBits(), GetBitmapDimension(), SetBitmapDimension(), GetDIBits(),
and SetDIBits(). These methods will assert if m_hBitmap is NULL.
- Other utility methods
CBitmapT has two useful methods that operate on m_hBitmap: LoadOEMBitmap()
and GetSize().
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 );
}
void func2()
{
somefunc ( _T("Willow Rosenberg") );
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 - like LoadIcon() - 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 a TCHAR buffer,
or assigned to a BSTR, depending on which overload you use. Note that these functions only accept
a UINT 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 the IMAGE_BITMAP type, passing along the fuLoad
flags.
HCURSOR AtlLoadCursorImage(
_U_STRINGorID cursor,
UINT fuLoad = LR_DEFAULTCOLOR | LR_DEFAULTSIZE,
int cxDesired = 0, int cyDesired = 0)
Calls LoadImage() with the IMAGE_CURSOR type, passing along the fuLoad
flags. Since one cursor resource can contain several different-sized cursors, you can pass dimensions for the cxDesired
and cyDesired 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 the IMAGE_ICON type, passing along the fuLoad
flags. The cxDesired and cyDesired parameters are used as in AtlLoadCursorImage().
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 the OBM_*
bitmaps listed in the LoadBitmap() documentation.
HCURSOR AtlLoadSysCursor(LPCTSTR lpCursorName)
Calls LoadCursor() with a NULL resource handle. Use this function to load any of the IDC_*
cursors listed in the LoadCursor() documentation.
HICON AtlLoadSysIcon(LPCTSTR lpIconName)
Calls LoadIcon() with a NULL resource handle. Use this function to load any of the IDI_*
icons listed in the LoadIcon() documentation. Note that this function - like LoadIcon()
- can only load 32x32 icons.
HBITMAP AtlLoadSysBitmapImage(
WORD wBitmapID, UINT fuLoad = LR_DEFAULTCOLOR)
Calls LoadImage() with a NULL resource handle and the IMAGE_BITMAP type. You can use
this function to load the same bitmaps as AtlLoadSysBitmap().
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 the IMAGE_CURSOR type. You can use
this function to load the same cursors as AtlLoadSysCursor().
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 the IMAGE_ICON type. You can use
this function to load the same icons as AtlLoadSysIcon(), 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
|
GetOpenFileName(),
GetSaveFileName()
|
CFileDialogImpl
|
CFileDialog
|
|
Choose Folder
|
SHBrowseForFolder()
|
CFolderDialogImpl
|
CFolderDialog
|
|
Choose Font
|
ChooseFont()
|
CFontDialogImpl,
CRichEditFontDialogImpl
|
CFontDialog,
CRichEditFontDialog
|
|
Choose Color
|
ChooseColor()
|
CColorDialogImpl
|
CColorDialog
|
|
Printing and Print Setup
|
PrintDlg()
|
CPrintDialogImpl
|
CPrintDialog
|
|
Printing (Windows 2000 and later)
|
PrintDlgEx()
|
CPrintDialogExImpl
|
CPrintDialogEx
|
|
Page Setup
|
PageSetupDlg()
|
CPageSetupDialogImpl
|
CPageSetupDialog
|
|
Text find and replace
|
FindText(),
ReplaceText()
|
CFindReplaceDialogImpl
|
CFindReplaceDialog
|
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() returns IDOK, get the selected file from m_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 of LPCTSTR.
- 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:
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 );
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:
CMyFileDialog(...);
BEGIN_MSG_MAP(CMyFileDialog)
CHAIN_MSG_MAP(CFileDialogImpl<CMyFileDialog>)
END_MSG_MAP()
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() returns IDOK, get the path to the selected folder from m_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:
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;
}
void OnInitialized()
{
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 a UINT or HMENU, and
is meant to be used in CreateWindow() wrappers. The hMenu parameter to CreateWindow()
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 member m_hMenu, which can be
passed as the hMenu parameter to CreateWindow() or CreateWindowEx().
_U_RECT: This type can be constructed from a LPRECT or RECT&, and
lets the caller pass in a RECT struct, pointer to a RECT, or a wrapper class like CRect
that provides a converter to RECT.
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
{
}
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, and 0x0800 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 becomes CIdleHandler, and DoUpdate()
becomes OnIdle().
_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 to CString::Format(). Defining _ATL_USE_CSTRING_FLOAT results
in CString::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 in CDCT 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 in CDCT 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 use CString.
_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 in CPropertyPage. 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, and CRect
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:
![[Kibbles initial state - 20K]](/KB/wtl/WTL4MFC9/kibbles0.png)
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:
![[Kibbles downloading - 22K]](/KB/wtl/WTL4MFC9/kibbles1.png)
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:
![[Colors options dlg - 13K]](/KB/wtl/WTL4MFC9/choosecolors.png)
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.
![[Danish duck background - 57K]](/KB/wtl/WTL4MFC9/kibbles2.png)
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.
Revision History
February 8, 2006: Article first published.
Series Navigation: « Part VIII (Property Sheets and Wizards)