WTL for MFC Programmers, Part IX - GDI Classes, Common Dialogs, and Utility Classes

WTL for MFC Programmers, Part IX - GDI Classes, Common Dialogs, and Utility Classes

 

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:

  1. GDI wrapper classes
  2. Resource-loading functions
  3. Using the file-open and choose-folder common dialogs
  4. 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:

  1. The destructor calls DeleteObject() on the wrapped handle, if it is not NULL.
  2. 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 = /* some HBITMAP value */;
 
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 = /* 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 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_hBitmapLoadOEMBitmap() 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 );
 
  // 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 - 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_PENBLACK_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_szFileNamem_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:

  1. Construct a CFileDialog object, passing any initial data to the constructor.
  2. Call DoModal().
  3. 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:

  1. The string parameters in the constructor are _U_STRINGorID instead of LPCTSTR.
  2. The filter string can use pipes to separate the fields, as in MFC, instead of null characters.
  3. 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_szFolderPathm_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:

  1. Construct a CFolderDialog object, passing any initial data to the constructor.
  2. Call DoModal().
  3. 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:
  // 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 CSizeCPoint, and CRect, that wrap the SIZEPOINT, 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
      {
      // 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 ftLastWriteTimeftLastAccessTime, 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 CSizeCPoint, 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]

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]

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]

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]

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

posted @ 2022-12-13 14:11  小风风的博客  阅读(70)  评论(0编辑  收藏  举报