65.9K
CodeProject 正在变化。 阅读更多。
Home

面向 MFC 程序员的 WTL,第九部分 - GDI 类、通用对话框和实用程序类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.99/5 (72投票s)

2006年2月8日

25分钟阅读

viewsIcon

288294

downloadIcon

4077

关于在 WTL 中使用 GDI 和通用对话框的教程。

目录

引言

WTL 包含许多包装器和实用程序类,例如 CStringCDC,它们在本系列中尚未完全介绍。WTL 有一个很好的系统来包装 GDI 对象,一些用于加载资源的有用函数,以及使 Win32 通用对话框更容易使用的类。在本系列第九部分中,我将介绍一些最常用的实用程序类。

本文讨论了四个类别的功能

  1. GDI 包装类
  2. 资源加载函数
  3. 使用文件打开和选择文件夹通用对话框
  4. 其他有用的类和全局函数

GDI 包装类

WTL 的 GDI 包装器与 MFC 的方法截然不同。WTL 的方法是为每种 GDI 对象类型提供一个模板类,每个模板都有一个名为 t_bManagedbool 参数。此参数控制该类的实例是否“管理”(或拥有)被包装的 GDI 对象。如果 t_bManagedfalse,则 C++ 对象不管理 GDI 对象的生命周期;C++ 对象是被包装 GDI 对象句柄的简单包装器。如果 t_bManagedtrue,则会发生两件事

  1. 析构函数在被包装句柄不为 NULL 时调用 DeleteObject()
  2. Attach() 在被包装句柄不为 NULL 时,在将 C++ 对象附加到新句柄之前调用 DeleteObject()

这种设计与 ATL 窗口类一致,其中 CWindowHWND 的普通包装器,而 CWindowImpl 管理窗口的生命周期。

GDI 包装类在 atlgdi.h 中定义,CMenuT 除外,它在 atluser.h 中。您不必自己包含这些头文件,因为 atlapp.h 总是为您包含它们。每个类也有 typedefs 具有更容易记住的名称

包装的 GDI 对象

模板类

托管对象的 Typedef

普通包装器的 Typedef

画笔

CPenT

CPen

CPenHandle

Brush

CBrushT

CBrush

CBrushHandle

字体

CFontT

CFont

CFontHandle

Bitmap

CBitmapT

CBitmap

CBitmapHandle

调色板

CPaletteT

CPalette

CPaletteHandle

区域

CRgnT

CRgn

CRgnHandle

设备上下文

CDCT

CDC

CDCHandle

Menu

CMenuT

CMenu

CMenuHandle

与 MFC 传递对象指针的方式相比,我更喜欢这种方法。您不必担心获取 NULL 指针(被包装的句柄可能是 NULL,但那是另一回事),也没有任何特殊情况会得到一个不能保持超过一个函数调用的临时对象。创建这些类中的任何一个实例也非常便宜,因为它们只有一个成员变量,即被包装的句柄。与 CWindow 一样,在线程之间传递包装类对象没有问题,因为 WTL 不像 MFC 那样维护线程特定的映射。

还有用于特殊绘图场景的额外设备上下文包装类

  • CClientDC:包装 GetDC()ReleaseDC() 的调用,用于在窗口的客户区绘图
  • CWindowDC:包装 GetWindowDC()ReleaseDC() 的调用,用于在窗口中的任何位置绘图。
  • CPaintDC:包装 BeginPaint()EndPaint() 的调用,用于 WM_PAINT 处理程序。

这些类中的每一个都在构造函数中接受一个 HWND,并且行为与同名的 MFC 类相似。所有这三个类都派生自 CDC,因此这些类都管理它们的设备上下文。

包装类中的常用函数

GDI 包装类遵循相同的设计。为了简洁起见,我将在这里介绍 CBitmapT 中的方法,但其他类的工作方式类似。

被包装的 GDI 对象句柄
每个类都保留一个公共成员变量,其中包含 C++ 对象关联的 GDI 对象句柄。CBitmapT 有一个名为 m_hBitmapHBITMAP 成员。
构造函数
构造函数有一个参数,一个 HBITMAP,默认为 NULL。m_hBitmap 初始化为该值。
析构函数
如果 t_bManaged 为 true,并且 m_hBitmap 不为 NULL,则析构函数调用 DeleteObject() 来销毁位图。
Attach()operator =
这些方法都接受一个 HBITMAP 句柄。如果 t_bManagedtrue,并且 m_hBitmap 不为 NULL,这些方法会调用 DeleteObject() 来销毁 CBitmapT 对象正在管理的位图。然后它们将 m_hBitmap 设置为作为参数传入的 HBITMAP
Detach()
Detach()m_hBitmap 设置为 NULL,然后返回 m_hBitmap 中的值。Detach() 返回后,CBitmapT 对象不再与 GDI 位图关联。
创建 GDI 对象的方法
CBitmapT 包含用于创建位图的 Win32 API 的包装器:LoadBitmap()LoadMappedBitmap()CreateBitmap()CreateBitmapIndirect()CreateCompatibleBitmap()CreateDiscardableBitmap()CreateDIBitmap()CreateDIBSection()。如果 m_hBitmap 不为 NULL,这些方法将断言;要将 CBitmapT 对象用于不同的 GDI 位图,请先调用 DeleteObject()Detach()
DeleteObject()
DeleteObject() 销毁 GDI 位图对象,然后将 m_hBitmap 设置为 NULL。此方法仅在 m_hBitmap 不为 NULL 时才应调用;否则将断言。
IsNull()
如果 m_hBitmap 为 NULL,则 IsNull() 返回 true,否则返回 false。使用此方法测试 CBitmapT 对象当前是否与 GDI 位图关联。
operator HBITMAP
此转换器返回 m_hBitmap,并允许您将 CBitmapT 对象传递给接受 HBITMAP 句柄的函数或 Win32 API。当 CBitmapT 在布尔上下文中求值时,也会调用此转换器,并且其逻辑结果与 IsNull() 相反。因此,这两个 if 语句是等效的
CBitmapHandle bmp = /* some HBITMAP value */;
 
if ( !bmp.IsNull() ) { do something... }
if ( bmp ) { do something more... }
GetObject() 包装器
CBitmapT 包含 Win32 API GetObject() 的类型安全包装器:GetBitmap()。它有两个重载:一个接受 LOGBITMAP* 并直接调用 GetObject();另一个接受 LOGBITMAP& 并返回一个表示成功的 bool。后一个版本更容易使用。例如
CBitmapHandle bmp2 = /* some HBITMAP value */;
LOGBITMAP logbmp = {0};
bool bSuccess;
 
if ( bmp2 )
  bSuccess = bmp2.GetLogBitmap ( logbmp );
操作 GDI 对象的 API 的包装器
CBitmapT 包含接受 HBITMAP 参数的 Win32 API 的包装器:GetBitmapBits()SetBitmapBits()GetBitmapDimension()SetBitmapDimension()GetDIBits()SetDIBits()。如果 m_hBitmap 为 NULL,这些方法将断言。
其他实用方法
CBitmapT 有两个有用的方法用于操作 m_hBitmapLoadOEMBitmap()GetSize()

使用 CDCT

CDCT 与其他类略有不同,因此我将单独介绍这些差异。

方法差异

销毁 DC 的方法称为 DeleteDC() 而不是 DeleteObject()

将对象选择到 DC 中

MFC 的 CDC 中一个容易出错的方面是将对象选择到 DC 中。MFC 的 CDC 有几个重载的 SelectObject() 函数,每个函数都接受指向不同 GDI 包装类(CPen*CBitmap* 等)的指针。如果您将 C++ 对象而不是 C++ 对象的指针传递给 SelectObject(),则代码最终会调用接受 HGDIOBJ 句柄的未文档化重载,这就是导致问题的原因。

WTL 的 CDCT 采用了更好的方法,并有几个选择方法,每个方法只适用于一种 GDI 对象类型

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)

在调试版本中,每个方法都断言 m_hDC 不为 NULL,并且参数是正确类型的 GDI 对象句柄。然后它们调用 SelectObject() API 并将 SelectObject() 的返回值转换为适当的类型。

还有一些辅助方法,它们使用给定常量调用 GetStockObject(),然后将对象选择到 DC 中

HPEN SelectStockPen(int nPen)
HBRUSH SelectStockBrush(int nBrush)
HFONT SelectStockFont(int nFont)
HPALETTE SelectStockPalette(int nPalette, BOOL bForceBackground)

与 MFC 包装类的区别

更少的构造函数:包装类缺少创建新 GDI 对象的构造函数。例如,MFC 的 CBrush 具有创建实心或图案画刷的构造函数。使用 WTL 类,您必须使用方法来创建 GDI 对象。

更好地选择对象到 DC 中:请参阅上面的“使用 CDCT”部分。

没有 m_hAttribDC:WTL 的 CDCT 没有 m_hAttribDC 成员。

某些方法中的次要参数差异:例如,MFC 中的 CDC::GetWindowExt() 返回一个 CSize 对象;而在 WTL 中,该方法返回一个 bool,并通过输出参数返回大小。

资源加载函数

WTL 有几个全局函数,它们是加载各种类型资源的有用快捷方式。在介绍这些函数之前,我们需要了解一个实用程序类:_U_STRINGorID

在 Win32 中,大多数类型的资源都可以通过字符串(LPCTSTR)或无符号整数(UINT)来标识。接受资源标识符的 API 接受 LPCTSTR 参数,如果您想传递 UINT,则需要使用 MAKEINTRESOURCE 宏将其转换为 LPCTSTR_U_STRINGorID 在用作资源标识符参数的类型时,隐藏了这种区别,因此调用者可以直接传递 UINTLPCTSTR。然后函数可以在必要时使用 CString 加载字符串

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 );
}

这是因为接受 LPCTSTRCString 构造函数会检查参数是否实际上是字符串 ID。如果是,则从字符串表中加载字符串并将其分配给 CString

在 VC 6 中,WTL 在 atlwinx.h 中提供了 _U_STRINGorID。在 VC 7 中,_U_STRINGorID 是 ATL 的一部分。无论哪种方式,其他 ATL/WTL 头文件都会始终为您包含类定义。

本节中的函数从 _Module 全局变量(在 VC 6 中)或 _AtlBaseModule 全局变量(在 VC 7 中)中保存的资源实例句柄加载资源。使用其他模块作为资源超出了本文的范围,因此我将不在此处介绍。请记住,默认情况下,这些函数在代码运行的 EXE 或 DLL 中查找。这些函数除了调用 API 之外没有做任何事情,它们的实用性在于 _U_STRINGorID 提供的简化资源标识符处理。

HACCEL AtlLoadAccelerators(_U_STRINGorID table)

调用 LoadAccelerators()

HMENU AtlLoadMenu(_U_STRINGorID menu)

调用 LoadMenu()

HBITMAP AtlLoadBitmap(_U_STRINGorID bitmap)

调用 LoadBitmap()

HCURSOR AtlLoadCursor(_U_STRINGorID cursor)

调用 LoadCursor()

HICON AtlLoadIcon(_U_STRINGorID icon)

调用 LoadIcon()。请注意,此函数 - 像 LoadIcon() 一样 - 只能加载 32x32 图标。

int AtlLoadString(UINT uID, LPTSTR lpBuffer, int nBufferMax)
bool AtlLoadString(UINT uID, BSTR& bstrText)

调用 LoadString()。字符串可以返回到 TCHAR 缓冲区中,或分配给 BSTR,具体取决于您使用的重载。请注意,这些函数只接受 UINT 作为资源 ID,因为字符串表条目不能有字符串标识符。

这组函数包装了对 LoadImage() 的调用,并接受传递给 LoadImage() 的附加参数。

HBITMAP AtlLoadBitmapImage(
          _U_STRINGorID bitmap, UINT fuLoad = LR_DEFAULTCOLOR)

使用 IMAGE_BITMAP 类型调用 LoadImage(),并传递 fuLoad 标志。

HCURSOR AtlLoadCursorImage(
          _U_STRINGorID cursor,
          UINT fuLoad = LR_DEFAULTCOLOR | LR_DEFAULTSIZE,
          int cxDesired = 0, int cyDesired = 0)

使用 IMAGE_CURSOR 类型调用 LoadImage(),并传递 fuLoad 标志。由于一个光标资源可以包含几个不同大小的光标,您可以为 cxDesiredcyDesired 参数传递维度以加载特定大小的光标。

HICON AtlLoadIconImage(
        _U_STRINGorID icon,
        UINT fuLoad = LR_DEFAULTCOLOR | LR_DEFAULTSIZE,
        int cxDesired = 0, int cyDesired = 0)

使用 IMAGE_ICON 类型调用 LoadImage(),并传递 fuLoad 标志。cxDesiredcyDesired 参数的使用方式与 AtlLoadCursorImage() 相同。

这组函数包装了加载系统定义资源(例如,标准手形光标)的调用。其中一些资源 ID(主要是位图的)默认不包含;您需要在 stdafx.h#define OEMRESOURCE 符号才能引用它们。

HBITMAP AtlLoadSysBitmap(LPCTSTR lpBitmapName)

使用 NULL 资源句柄调用 LoadBitmap()。使用此函数加载 LoadBitmap() 文档中列出的任何 OBM_* 位图。

HCURSOR AtlLoadSysCursor(LPCTSTR lpCursorName)

使用 NULL 资源句柄调用 LoadCursor()。使用此函数加载 LoadCursor() 文档中列出的任何 IDC_* 光标。

HICON AtlLoadSysIcon(LPCTSTR lpIconName)

使用 NULL 资源句柄调用 LoadIcon()。使用此函数加载 LoadIcon() 文档中列出的任何 IDI_* 图标。请注意,此函数 - 像 LoadIcon() 一样 - 只能加载 32x32 图标。

HBITMAP AtlLoadSysBitmapImage(
          WORD wBitmapID, UINT fuLoad = LR_DEFAULTCOLOR)

使用 NULL 资源句柄和 IMAGE_BITMAP 类型调用 LoadImage()。您可以使用此函数加载与 AtlLoadSysBitmap() 相同的位图。

HCURSOR AtlLoadSysCursorImage(
         _U_STRINGorID cursor,
          UINT fuLoad = LR_DEFAULTCOLOR | LR_DEFAULTSIZE,
          int cxDesired = 0, int cyDesired = 0)

使用 NULL 资源句柄和 IMAGE_CURSOR 类型调用 LoadImage()。您可以使用此函数加载与 AtlLoadSysCursor() 相同的光标。

HICON AtlLoadSysIconImage(
        _U_STRINGorID icon,
        UINT fuLoad = LR_DEFAULTCOLOR | LR_DEFAULTSIZE,
        int cxDesired = 0, int cyDesired = 0)

使用 NULL 资源句柄和 IMAGE_ICON 类型调用 LoadImage()。您可以使用此函数加载与 AtlLoadSysIcon() 相同的图标,但您也可以指定不同的大小,例如 16x16。

最后,这组函数是 GetStockObject() API 的类型安全包装器。

HPEN AtlGetStockPen(int nPen)
HBRUSH AtlGetStockBrush(int nBrush)
HFONT AtlGetStockFont(int nFont)
HPALETTE AtlGetStockPalette(int nPalette)

每个函数都会检查您是否传递了合理的值(例如,AtlGetStockPen() 只接受 WHITE_PENBLACK_PEN 等),然后调用 GetStockObject()

使用通用对话框

WTL 具有使 Win32 通用对话框更容易使用的类。每个类都处理通用对话框发送的消息和回调,并反过来调用可重写函数。这与属性表中使用相同的设计,您为各个属性表通知(例如,处理 PSN_WIZNEXTOnWizardNext())编写处理程序,这些处理程序在必要时由 CPropertyPageImpl 调用。

WTL 为每个通用对话框包含两个类;例如,“选择文件夹”对话框由 CFolderDialogImplCFolderDialog 包装。如果您需要更改任何默认值或为任何消息编写处理程序,则从 CFolderDialogImpl 派生一个新类并在该类中进行更改。如果 CFolderDialogImpl 的默认行为足够,您可以使用 CFolderDialog

通用对话框及其对应的 WTL 类是

通用对话框

对应的 Win32 API

实现类

不可自定义类

文件打开和文件保存

GetOpenFileName(),
GetSaveFileName()

CFileDialogImpl

CFileDialog

选择文件夹

SHBrowseForFolder()

CFolderDialogImpl

CFolderDialog

选择字体

ChooseFont()

CFontDialogImpl,
CRichEditFontDialogImpl

CFontDialog,
CRichEditFontDialog

选择颜色

ChooseColor()

CColorDialogImpl

CColorDialog

打印和打印设置

PrintDlg()

CPrintDialogImpl

CPrintDialog

打印(Windows 2000 及更高版本)

PrintDlgEx()

CPrintDialogExImpl

CPrintDialogEx

页面设置

PageSetupDlg()

CPageSetupDialogImpl

CPageSetupDialog

文本查找和替换

FindText(),
ReplaceText()

CFindReplaceDialogImpl

CFindReplaceDialog

由于编写所有这些类会使本文过长,我将只介绍前两个,它们是您最常使用的类。

CFileDialog

CFileDialog 及其基类 CFileDialogImpl 用于显示“文件打开”和“文件保存”对话框。CFileDialogImpl 中最重要的两个数据成员是 m_ofnm_szFileNamem_ofn 是一个 OPENFILENAME 结构,CFileDialogImpl 会为您设置一些有意义的默认值;就像在 MFC 中一样,如果需要,您可以直接更改此结构中的数据。m_szFileName 是一个 TCHAR 数组,用于保存选定文件的名称。(由于 CFileDialogImpl 只有一个字符串用于保存文件名,因此当您使用多选文件打开对话框时,需要提供自己的缓冲区。)

使用 CFileDialog 的基本步骤是

  1. 构造一个 CFileDialog 对象,并将任何初始数据传递给构造函数。
  2. 调用 DoModal()
  3. 如果 DoModal() 返回 IDOK,则从 m_szFileName 获取选定的文件。

这是 CFileDialog 构造函数

CFileDialog::CFileDialog (
    BOOL bOpenFileDialog,
    LPCTSTR lpszDefExt = NULL,
    LPCTSTR lpszFileName = NULL,
    DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
    LPCTSTR lpszFilter = NULL,
    HWND hWndParent = NULL )

bOpenFileDialog 应为 true 以创建“文件打开”对话框(CFileDialog 将调用 GetOpenFileName() 显示对话框),或 false 以创建“文件保存”对话框(CFileDialog 将调用 GetSaveFileName())。其余参数直接存储在 m_ofn 结构的相应成员中,但它们是可选的,因为您可以在调用 DoModal() 之前直接访问 m_ofn

MFC 的 CFileDialog 与 WTL 的 CFileDialog 之间的一个显著区别是,lpszFilter 参数必须是 null 字符分隔的字符串列表(即,OPENFILENAME 文档中描述的格式),而不是管道分隔列表。

这是一个使用 CFileDialog 的示例,其中过滤器选择 Word 12 文件(*.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 对本地化不是很友好,因为构造函数使用 LPCTSTR 参数。那个过滤器字符串乍一看也有些难以阅读。有两种解决方案,要么在调用 DoModal() 之前设置 m_ofn,要么从 CFileDialogImpl 派生一个新类,并进行我们想要的改进。我们在这里采用第二种方法,创建一个具有以下更改的新类

  1. 构造函数中的字符串参数是 _U_STRINGorID 而不是 LPCTSTR
  2. 过滤器字符串可以使用管道分隔字段,如 MFC 中那样,而不是 null 字符。
  3. 对话框将自动相对于其父窗口居中。

我们将从编写一个类的构造函数开始,该构造函数接受与 CFileDialogImpl 构造函数类似的参数

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;
};

构造函数初始化三个 CString 成员,必要时加载字符串

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)
{
}

请注意,在调用基类构造函数时,字符串参数都为 NULL。这是因为基类构造函数总是在成员初始化器之前调用。为了在 m_ofn 中设置字符串数据,我们添加了一些代码,这些代码复制了 CFileDialogImpl 构造函数会执行的初始化步骤

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() 是一个辅助方法,它接受一个以管道分隔的过滤器字符串,将管道更改为 null 字符,并返回指向字符串开头的指针。结果是一个字符串列表,其格式适合在 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;
}

这些更改使字符串处理更容易。为了实现自动居中,我们将重写 OnInitDone() 通知。这需要我们添加一个消息映射(以便我们可以将通知消息链接到基类)和我们的 OnInitDone() 处理程序

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;
};

附加到 CMyFileDialog 对象的窗口实际上是“文件打开”对话框的子窗口。由于我们需要堆栈中最顶层的窗口,我们调用 GetFileDialogWindow() 来获取该窗口。

CFolderDialog

CFolderDialog 及其基类 CFolderDialogImpl 用于显示“浏览文件夹”对话框。虽然该对话框支持在 shell 命名空间中的任何位置进行浏览,但 CFolderDialog 只能在文件系统中进行浏览。CFolderDialogImpl 中最重要的两个数据成员是 m_bim_szFolderPathm_bi 是一个 BROWSEINFO 结构,CFolderDialogImpl 对其进行管理并将其传递给 SHBrowseForFolder() API;如果需要,您可以直接更改此结构中的数据。m_szFolderPath 是一个 TCHAR 数组,用于保存选定文件夹的名称。

使用 CFolderDialog 的基本步骤是

  1. 构造一个 CFolderDialog 对象,并将任何初始数据传递给构造函数。
  2. 调用 DoModal()
  3. 如果 DoModal() 返回 IDOK,则从 m_szFolderPath 获取选定文件夹的路径。

这是 CFolderDialog 构造函数

CFolderDialog::CFolderDialog (
    HWND hWndParent = NULL,
    LPCTSTR lpstrTitle = NULL,
    UINT uFlags = BIF_RETURNONLYFSDIRS )

hWndParent 是浏览对话框的所有者窗口。您可以在构造函数中设置它,也可以在 DoModal() 调用中设置它。lpstrTitle 是一个将在对话框树形控件上方显示的字符串。uFlags 是控制对话框行为的标志,应始终包含 BIF_RETURNONLYFSDIRS,以便树形控件仅显示文件系统目录。您可以使用 BROWSEINFO 文档中列出的其他 uFlags 值,但请记住,某些标志可能不会产生好的结果,例如 BIF_BROWSEFORPRINTER。与 UI 相关的标志(如 BIF_USENEWUI)将正常工作。请注意,lpstrTitle 参数与 CFileDialog 构造函数中的字符串具有相同的可用性问题。

这是使用 CFolderDialog 选择目录的示例

CString sSelectedDir;
CFolderDialog fldDlg ( NULL, _T("Select a dir"),
                       BIF_RETURNONLYFSDIRS|BIF_NEWDIALOGSTYLE );
 
  if ( IDOK == fldDlg.DoModal() )
    sSelectedDir = fldDlg.m_szFolderPath;

为了演示自定义 CFolderDialog,我们将从 CFolderDialogImpl 派生一个类并设置初始选择。此对话框的回调不使用窗口消息,因此该类不需要消息映射。相反,我们重写 OnInitialized() 方法,该方法在基类收到 BFFM_INITIALIZED 通知时调用。OnInitialized() 调用 CFolderDialogImpl::SetSelection() 以更改对话框中的选择。

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;
};

其他有用的类和全局函数

结构包装器

WTL 具有 CSizeCPointCRect 类,它们分别包装 SIZEPOINTRECT 结构。它们的工作方式与 MFC 中的对应类相同。

处理双类型参数的类

如前所述,您可以将 _U_STRINGorID 类型用于可以是数字或字符串资源 ID 的函数参数。还有两个工作方式类似的类

  • _U_MENUorID:此类型可以从 UINTHMENU 构造,旨在用于 CreateWindow() 包装器中。CreateWindow()hMenu 参数在创建的窗口是子窗口时实际上是一个窗口 ID,因此 _U_MENUorID 隐藏了这两种用法之间的区别。_U_MENUorID 有一个成员 m_hMenu,可以作为 hMenu 参数传递给 CreateWindow()CreateWindowEx()
  • _U_RECT:此类型可以从 LPRECTRECT& 构造,并允许调用者传入 RECT 结构、指向 RECT 的指针或提供到 RECT 的转换器的包装类,例如 CRect

_U_STRINGorID 一样,_U_MENUorID_U_RECT 总是由其他头文件为您包含。

其他实用程序类

CString

WTL 的 CString 的工作方式与 MFC 的 CString 完全相同,因此我将不在此处详细介绍。当您使用定义了 _ATL_MIN_CRT 构建时,WTL 的 CString 具有许多额外的函数。这些函数,例如 _cstrchr()_cstrstr(),是相应 CRT 函数的替代品,当定义了 _ATL_MIN_CRT 时,这些函数不可用。

CFindFile

CFindFile 包装了 FindFirstFile()FindNextFile() API,并且比 MFC 的 CFileFind 更易于使用。一般的使用模式如下

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();

如果 FindFirstFile() 返回 true,则至少有一个文件与模式匹配。在 do 循环中,您可以访问公共 CFindFile 成员 m_fd,它是一个 WIN32_FIND_DATA 结构,其中包含有关找到的文件的信息。循环继续,直到 FindNextFile() 返回 false,表示所有文件都已枚举。

CFindFile 具有以更易于使用的形式返回 m_fd 中数据的方法。这些方法仅在成功调用 FindFirstFile()FindNextFile() 后才返回有意义的值。

ULONGLONG GetFileSize()

将文件大小作为 64 位无符号整数返回。

BOOL GetFileName(LPTSTR lpstrFileName, int cchLength)
CString GetFileName()

返回找到文件的文件名和扩展名(从 m_fd.cFileName 复制)。

BOOL GetFilePath(LPTSTR lpstrFilePath, int cchLength)
CString GetFilePath()

返回找到文件的完整路径。

BOOL GetFileTitle(LPTSTR lpstrFileTitle, int cchLength)
CString GetFileTitle()

仅返回找到文件的文件标题(即,不带扩展名的文件名)。

BOOL GetFileURL(LPTSTR lpstrFileURL, int cchLength)
CString GetFileURL()

创建一个包含文件完整路径的 file:// URL。

BOOL GetRoot(LPTSTR lpstrRoot, int cchLength)
CString GetRoot()

返回包含文件的目录。

BOOL GetLastWriteTime(FILETIME* pTimeStamp)
BOOL GetLastAccessTime(FILETIME* pTimeStamp)
BOOL GetCreationTime(FILETIME* pTimeStamp)

这些方法分别返回 m_fd 中的 ftLastWriteTimeftLastAccessTimeftCreationTime 成员。

CFindFile 还有一些辅助方法用于检查找到的文件的属性。

BOOL IsDots()

如果找到的文件是“.”或“..”目录,则返回 true

BOOL MatchesMask(DWORD dwMask)

dwMask 中的位(应为 FILE_ATTRIBUTE_* 常量)与找到文件的属性进行比较。如果 dwMask 中所有已打开的位在文件属性中也已打开,则返回 true

BOOL IsReadOnly()
BOOL IsDirectory()
BOOL IsCompressed()
BOOL IsSystem()
BOOL IsHidden()
BOOL IsTemporary()
BOOL IsNormal()
BOOL IsArchived()

这些方法是快捷方式,它们使用特定的 FILE_ATTRIBUTE_* 位调用 MatchesMask()。例如,IsReadOnly() 调用 MatchesMask(FILE_ATTRIBUTE_READONLY)

全局函数

WTL 有几个有用的全局函数,您可以使用它们来执行 DLL 版本检查和显示消息框等操作。

bool AtlIsOldWindows()

如果操作系统是 Windows 95、98、NT 3 或 NT 4,则返回 true。

HFONT AtlGetDefaultGuiFont()

返回 GetStockObject(DEFAULT_GUI_FONT) 的值。在英文版 Windows 2000 及更高版本(以及使用拉丁字母的其他单字节语言)中,此字体的字形名称是“MS Shell Dlg”。这可以用作对话框字体,但如果您正在创建自己的字体以在 UI 中使用,则这不是最佳选择。MS Shell Dlg 是 MS Sans Serif 的别名,而不是新的 UI 字体 Tahoma。为了避免使用 MS Sans Serif,您可以使用以下代码获取用于消息框的字体

NONCLIENTMETRICS ncm = { sizeof(NONCLIENTMETRICS) };
CFont font;
 
  if ( SystemParametersInfo ( SPI_GETNONCLIENTMETRICS, 0, &ncm, false ) )
    font.CreateFontIndirect ( &ncm.lfMessageFont );

另一种方法是检查 AtlGetDefaultGuiFont() 返回的字体的字形名称。如果名称是“MS Shell Dlg”,您可以将其更改为“MS Shell Dlg 2”,这是解析为 Tahoma 的别名。

HFONT AtlCreateBoldFont(HFONT hFont = NULL)

创建给定字体的粗体版本。如果 hFont 为 NULL,则 AtlCreateBoldFont() 会创建 AtlGetDefaultGuiFont() 返回字体的粗体版本。

BOOL AtlInitCommonControls(DWORD dwFlags)

这是 InitCommonControlsEx() API 的包装器。它使用给定标志初始化 INITCOMMONCONTROLSEX 结构,然后调用该 API。

HRESULT AtlGetDllVersion(HINSTANCE hInstDLL, DLLVERSIONINFO* pDllVersionInfo)
HRESULT AtlGetDllVersion(LPCTSTR lpstrDllName, DLLVERSIONINFO* pDllVersionInfo)

这些函数在给定模块中查找名为 DllGetVersion() 的导出函数。如果找到该函数,则调用它。如果 DllGetVersion() 成功,它会在 DLLVERSIONINFO 结构中返回版本信息。

HRESULT AtlGetCommCtrlVersion(LPDWORD pdwMajor, LPDWORD pdwMinor)

返回 comctl32.dll 的主版本和次版本。

HRESULT AtlGetShellVersion(LPDWORD pdwMajor, LPDWORD pdwMinor)

返回 shell32.dll 的主版本和次版本。

bool AtlCompactPath(LPTSTR lpstrOut, LPCTSTR lpstrIn, int cchLen)

截断文件路径,使其长度小于 cchLen 个字符,如果路径太长,则在末尾添加省略号。这与 shlwapi.dll 中的 PathCompactPath()PathSetDlgItemPath() 函数类似。

int AtlMessageBox(HWND hWndOwner, _U_STRINGorID message,
                  _U_STRINGorID title = NULL,
                  UINT uType = MB_OK | MB_ICONINFORMATION)

显示一个消息框,如 MessageBox(),但使用 _U_STRINGorID 参数,因此您可以传递字符串资源 ID。AtlMessageBox() 会在必要时处理字符串加载。

在 WTL 头文件中,您会看到各种预处理器宏被引用。大多数这些宏都可以在编译器设置中设置,以更改 WTL 代码中的行为。

这些宏是预定义的或通过构建设置设置的,您会在整个 WTL 代码中看到它们被引用

_WTL_VER
WTL 7.1 定义为 0x0710
_ATL_MIN_CRT
如果定义,ATL 不链接到 C 运行时库。由于某些 WTL 类(特别是 CString)通常使用 CRT 函数,因此会编译特殊代码来替换通常从 CRT 导入的代码。
_ATL_VER
VC 6 预定义为 0x0300,VC 7 预定义为 0x0700,VC 8 预定义为 0x0800
_WIN32_WCE
如果当前编译是针对 Windows CE 二进制文件,则定义。当相应功能在 CE 中不可用时,一些 WTL 代码会被禁用。

以下宏默认未定义。要使用宏,请在 stdafx.h 中所有 #include 语句之前 #define 它。

_ATL_NO_OLD_NAMES
此宏仅在维护 WTL 3 代码时有用。它添加了一些编译器指令以识别两个旧类名:CUpdateUIObject 变为 CIdleHandlerDoUpdate() 变为 OnIdle()
_ATL_USE_CSTRING_FLOAT
定义此符号以在 CString 中启用浮点支持;_ATL_MIN_CRT 不得同时定义。如果您打算在传递给 CString::Format() 的格式字符串中使用 %I64 前缀,则需要定义此符号。定义 _ATL_USE_CSTRING_FLOAT 会导致 CString::Format() 调用 _vstprintf(),后者可以理解 %I64 前缀。
_ATL_USE_DDX_FLOAT
定义此符号以在 DDX 代码中启用浮点支持;_ATL_MIN_CRT 不得同时定义。
_ATL_NO_MSIMG
定义此符号可防止编译器看到 #pragma comment(lib, "msimg32") 行;同时禁用 CDCT 中使用 msimg32 函数的代码:AlphaBlend()TransparentBlt()GradientFill()
_ATL_NO_OPENGL
定义此符号可防止编译器看到 #pragma comment(lib, "opengl32") 行;同时禁用 CDCT 中使用 OpenGL 的代码。
_WTL_FORWARD_DECLARE_CSTRING
已废弃,请改用 _WTL_USE_CSTRING
_WTL_USE_CSTRING
定义此符号以向前声明 CString。这样,通常在 atlmisc.h 之前包含的头文件中的代码将能够使用 CString
_WTL_NO_CSTRING
定义此符号可阻止使用 WTL::CString
_WTL_NO_AUTOMATIC_NAMESPACE
定义此符号以阻止自动执行 using namespace WTL 指令。
_WTL_NO_AUTO_THEME
定义此符号可阻止 CMDICommandBarCtrlImpl 使用 XP 主题。
_WTL_NEW_PAGE_NOTIFY_HANDLERS
定义此符号可在 CPropertyPage 中使用较新的 PSN_* 通知处理程序。由于旧的 WTL 3 处理程序已过时,因此除非您正在维护无法更新的 WTL 3 代码,否则应始终定义此符号。
_WTL_NO_WTYPES
定义此符号可阻止定义 WTL 版本的 CSizeCPointCRect
_WTL_NO_THEME_DELAYLOAD
使用 VC 6 构建时,定义此符号可防止 uxtheme.dll 被自动标记为延迟加载 DLL。

注意:如果既未定义 _WTL_USE_CSTRING 也未定义 _WTL_NO_CSTRING,则在包含 atlmisc.h 之后,可以在任何时候使用 CString

示例项目

本文的演示项目是一个名为 Kibbles 的下载器应用程序,它演示了本文中介绍的各种类。它使用您可以在 Windows 2000 及更高版本上获得的 BITS(后台智能传输服务)组件;由于此应用程序仅在基于 NT 的操作系统上运行,因此我也将其制作成一个 Unicode 项目。

该应用程序有一个视图窗口,显示下载进度,使用各种 GDI 调用,包括绘制饼图形状的 Pie()。当应用程序首次运行时,您将看到 UI 的初始状态

 [Kibbles initial state - 20K]

您可以将浏览器中的链接拖到窗口中,创建一个新的 BITS 任务,将链接的目标下载到您的“我的文档”文件夹。您还可以单击第三个工具栏按钮,将任何您想要的 URL 添加到任务中。第四个按钮允许您更改默认下载目录。

当下载任务正在进行时,Kibbles 会显示一些关于任务的详细信息,并显示下载进度,如下所示

 [Kibbles downloading - 22K]

工具栏中的前两个按钮允许您更改进度显示中使用的颜色。第一个按钮打开一个选项对话框,您可以在其中设置显示各个部分使用的颜色

 [Colors options dlg - 13K]

该对话框使用了 Tim Smith 文章 WTL 带 XP 主题的颜色选择器 中出色的按钮类;请查看 Kibbles 项目中的 CChooseColorsDlg 类,了解其实际运行情况。“文本颜色”按钮是一个常规按钮,OnChooseTextColor() 处理程序演示了如何使用 WTL 类 CColorDialog。第二个工具栏按钮将所有颜色更改为随机值。

第五个按钮允许您设置背景图片,该图片将绘制在饼图显示已下载部分的区域中。默认图片作为资源包含在内,但如果您的“我的图片”目录中有任何 BMP 文件,您也可以选择其中一个。

 [Danish duck background - 57K]

CMainFrame::OnToolbarDropdown() 包含处理按钮按下事件和显示弹出菜单的代码。该函数还使用 CFindFile 枚举“我的图片”目录的内容。您可以查看 CKibblesView::OnPaint(),了解执行各种 GDI 操作以绘制 UI 的代码。

关于工具栏的一个重要说明:工具栏使用 256 色位图,但 VC 工具栏编辑器只支持 16 色位图。如果您使用编辑器编辑工具栏,VC 会将位图颜色减少到 16 色。我建议的做法是:将高色版本位图保存在单独的目录中,直接使用图形程序对其进行更改,然后将 256 色版本保存在 res 目录中。

版权和许可

本文受版权保护,版权所有 (c)2006 Michael Dunn。我意识到这并不能阻止人们在网络上随意复制,但我仍然要声明。如果您有兴趣翻译本文,请给我发电子邮件告知。我预计不会拒绝任何人翻译的许可,我只是想了解翻译情况,以便在此处发布链接。

除了 ColorButton.cppColorButton.h,本文随附的演示代码已发布到公共领域。我以这种方式发布,以便代码可以造福所有人。(我没有将文章本身发布到公共领域,因为仅在 CodeProject 上提供文章有助于提高我的知名度和 CodeProject 网站。)如果您在自己的应用程序中使用演示代码,如果您能给我发一封电子邮件告知我将不胜感激(只是为了满足我的好奇心,想知道人们是否从我的代码中受益),但这不是必需的。在您自己的源代码中注明出处也值得赞赏,但不是必需的。

文件 ColorButton.cppColorButton.h 来自 Tim Smith 的 WTL 带 XP 主题的颜色选择器。它们不受上述许可声明的约束;请参阅这些文件中的注释以了解其许可。

修订历史

2006 年 2 月 8 日:文章首次发表。

系列导航:« 第八部分(属性表和向导)

© . All rights reserved.