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:
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.
Revision History
February 8, 2006: Article first published.
Series Navigation: « Part VIII (Property Sheets and Wizards)