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






4.99/5 (72投票s)
2006年2月8日
25分钟阅读

288294

4077
关于在 WTL 中使用 GDI 和通用对话框的教程。
目录
引言
WTL 包含许多包装器和实用程序类,例如 CString
和 CDC
,它们在本系列中尚未完全介绍。WTL 有一个很好的系统来包装 GDI 对象,一些用于加载资源的有用函数,以及使 Win32 通用对话框更容易使用的类。在本系列第九部分中,我将介绍一些最常用的实用程序类。
本文讨论了四个类别的功能
- GDI 包装类
- 资源加载函数
- 使用文件打开和选择文件夹通用对话框
- 其他有用的类和全局函数
GDI 包装类
WTL 的 GDI 包装器与 MFC 的方法截然不同。WTL 的方法是为每种 GDI 对象类型提供一个模板类,每个模板都有一个名为 t_bManaged
的 bool
参数。此参数控制该类的实例是否“管理”(或拥有)被包装的 GDI 对象。如果 t_bManaged
为 false
,则 C++ 对象不管理 GDI 对象的生命周期;C++ 对象是被包装 GDI 对象句柄的简单包装器。如果 t_bManaged
为 true
,则会发生两件事
- 析构函数在被包装句柄不为 NULL 时调用
DeleteObject()
。 Attach()
在被包装句柄不为 NULL 时,在将 C++ 对象附加到新句柄之前调用DeleteObject()
。
这种设计与 ATL 窗口类一致,其中 CWindow
是 HWND
的普通包装器,而 CWindowImpl
管理窗口的生命周期。
GDI 包装类在 atlgdi.h 中定义,CMenuT
除外,它在 atluser.h 中。您不必自己包含这些头文件,因为 atlapp.h 总是为您包含它们。每个类也有 typedefs 具有更容易记住的名称
包装的 GDI 对象 |
模板类 |
托管对象的 Typedef |
普通包装器的 Typedef |
---|---|---|---|
画笔 |
|
|
|
Brush |
|
|
|
字体 |
|
|
|
Bitmap |
|
|
|
调色板 |
|
|
|
区域 |
|
|
|
设备上下文 |
|
|
|
Menu |
|
|
|
与 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_hBitmap
的HBITMAP
成员。 - 构造函数
- 构造函数有一个参数,一个
HBITMAP
,默认为 NULL。m_hBitmap
初始化为该值。 - 析构函数
- 如果
t_bManaged
为 true,并且m_hBitmap
不为 NULL,则析构函数调用DeleteObject()
来销毁位图。 Attach()
和operator =
- 这些方法都接受一个
HBITMAP
句柄。如果t_bManaged
为true
,并且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 APIGetObject()
的类型安全包装器: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_hBitmap
:LoadOEMBitmap()
和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
在用作资源标识符参数的类型时,隐藏了这种区别,因此调用者可以直接传递 UINT
或 LPCTSTR
。然后函数可以在必要时使用 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 ); }
这是因为接受 LPCTSTR
的 CString
构造函数会检查参数是否实际上是字符串 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
标志。由于一个光标资源可以包含几个不同大小的光标,您可以为cxDesired
和cyDesired
参数传递维度以加载特定大小的光标。HICON AtlLoadIconImage( _U_STRINGorID icon, UINT fuLoad = LR_DEFAULTCOLOR | LR_DEFAULTSIZE, int cxDesired = 0, int cyDesired = 0)使用
IMAGE_ICON
类型调用LoadImage()
,并传递fuLoad
标志。cxDesired
和cyDesired
参数的使用方式与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_PEN
、BLACK_PEN
等),然后调用 GetStockObject()
。
使用通用对话框
WTL 具有使 Win32 通用对话框更容易使用的类。每个类都处理通用对话框发送的消息和回调,并反过来调用可重写函数。这与属性表中使用相同的设计,您为各个属性表通知(例如,处理 PSN_WIZNEXT
的 OnWizardNext()
)编写处理程序,这些处理程序在必要时由 CPropertyPageImpl
调用。
WTL 为每个通用对话框包含两个类;例如,“选择文件夹”对话框由 CFolderDialogImpl
和 CFolderDialog
包装。如果您需要更改任何默认值或为任何消息编写处理程序,则从 CFolderDialogImpl
派生一个新类并在该类中进行更改。如果 CFolderDialogImpl
的默认行为足够,您可以使用 CFolderDialog
。
通用对话框及其对应的 WTL 类是
通用对话框 |
对应的 Win32 API |
实现类 |
不可自定义类 |
---|---|---|---|
文件打开和文件保存 |
|
|
|
选择文件夹 |
|
|
|
选择字体 |
|
|
|
选择颜色 |
|
|
|
打印和打印设置 |
|
|
|
打印(Windows 2000 及更高版本) |
|
|
|
页面设置 |
|
|
|
文本查找和替换 |
|
|
|
由于编写所有这些类会使本文过长,我将只介绍前两个,它们是您最常使用的类。
CFileDialog
CFileDialog
及其基类 CFileDialogImpl
用于显示“文件打开”和“文件保存”对话框。CFileDialogImpl
中最重要的两个数据成员是 m_ofn
和 m_szFileName
。m_ofn
是一个 OPENFILENAME
结构,CFileDialogImpl
会为您设置一些有意义的默认值;就像在 MFC 中一样,如果需要,您可以直接更改此结构中的数据。m_szFileName
是一个 TCHAR
数组,用于保存选定文件的名称。(由于 CFileDialogImpl
只有一个字符串用于保存文件名,因此当您使用多选文件打开对话框时,需要提供自己的缓冲区。)
使用 CFileDialog
的基本步骤是
- 构造一个
CFileDialog
对象,并将任何初始数据传递给构造函数。 - 调用
DoModal()
。 - 如果
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
派生一个新类,并进行我们想要的改进。我们在这里采用第二种方法,创建一个具有以下更改的新类
- 构造函数中的字符串参数是
_U_STRINGorID
而不是LPCTSTR
。 - 过滤器字符串可以使用管道分隔字段,如 MFC 中那样,而不是 null 字符。
- 对话框将自动相对于其父窗口居中。
我们将从编写一个类的构造函数开始,该构造函数接受与 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_bi
和 m_szFolderPath
。m_bi
是一个 BROWSEINFO
结构,CFolderDialogImpl
对其进行管理并将其传递给 SHBrowseForFolder()
API;如果需要,您可以直接更改此结构中的数据。m_szFolderPath
是一个 TCHAR
数组,用于保存选定文件夹的名称。
使用 CFolderDialog
的基本步骤是
- 构造一个
CFolderDialog
对象,并将任何初始数据传递给构造函数。 - 调用
DoModal()
。 - 如果
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 具有 CSize
、CPoint
和 CRect
类,它们分别包装 SIZE
、POINT
和 RECT
结构。它们的工作方式与 MFC 中的对应类相同。
处理双类型参数的类
如前所述,您可以将 _U_STRINGorID
类型用于可以是数字或字符串资源 ID 的函数参数。还有两个工作方式类似的类
_U_MENUorID
:此类型可以从UINT
或HMENU
构造,旨在用于CreateWindow()
包装器中。CreateWindow()
的hMenu
参数在创建的窗口是子窗口时实际上是一个窗口 ID,因此_U_MENUorID
隐藏了这两种用法之间的区别。_U_MENUorID
有一个成员m_hMenu
,可以作为hMenu
参数传递给CreateWindow()
或CreateWindowEx()
。_U_RECT
:此类型可以从LPRECT
或RECT&
构造,并允许调用者传入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
中的 ftLastWriteTime
、ftLastAccessTime
和 ftCreationTime
成员。
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
变为CIdleHandler
,DoUpdate()
变为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 版本的
CSize
、CPoint
和CRect
。 _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 的初始状态
您可以将浏览器中的链接拖到窗口中,创建一个新的 BITS 任务,将链接的目标下载到您的“我的文档”文件夹。您还可以单击第三个工具栏按钮,将任何您想要的 URL 添加到任务中。第四个按钮允许您更改默认下载目录。
当下载任务正在进行时,Kibbles 会显示一些关于任务的详细信息,并显示下载进度,如下所示
工具栏中的前两个按钮允许您更改进度显示中使用的颜色。第一个按钮打开一个选项对话框,您可以在其中设置显示各个部分使用的颜色
该对话框使用了 Tim Smith 文章 WTL 带 XP 主题的颜色选择器 中出色的按钮类;请查看 Kibbles 项目中的 CChooseColorsDlg
类,了解其实际运行情况。“文本颜色”按钮是一个常规按钮,OnChooseTextColor()
处理程序演示了如何使用 WTL 类 CColorDialog
。第二个工具栏按钮将所有颜色更改为随机值。
第五个按钮允许您设置背景图片,该图片将绘制在饼图显示已下载部分的区域中。默认图片作为资源包含在内,但如果您的“我的图片”目录中有任何 BMP 文件,您也可以选择其中一个。
CMainFrame::OnToolbarDropdown()
包含处理按钮按下事件和显示弹出菜单的代码。该函数还使用 CFindFile
枚举“我的图片”目录的内容。您可以查看 CKibblesView::OnPaint()
,了解执行各种 GDI 操作以绘制 UI 的代码。
关于工具栏的一个重要说明:工具栏使用 256 色位图,但 VC 工具栏编辑器只支持 16 色位图。如果您使用编辑器编辑工具栏,VC 会将位图颜色减少到 16 色。我建议的做法是:将高色版本位图保存在单独的目录中,直接使用图形程序对其进行更改,然后将 256 色版本保存在 res
目录中。
版权和许可
本文受版权保护,版权所有 (c)2006 Michael Dunn。我意识到这并不能阻止人们在网络上随意复制,但我仍然要声明。如果您有兴趣翻译本文,请给我发电子邮件告知。我预计不会拒绝任何人翻译的许可,我只是想了解翻译情况,以便在此处发布链接。
除了 ColorButton.cpp 和 ColorButton.h,本文随附的演示代码已发布到公共领域。我以这种方式发布,以便代码可以造福所有人。(我没有将文章本身发布到公共领域,因为仅在 CodeProject 上提供文章有助于提高我的知名度和 CodeProject 网站。)如果您在自己的应用程序中使用演示代码,如果您能给我发一封电子邮件告知我将不胜感激(只是为了满足我的好奇心,想知道人们是否从我的代码中受益),但这不是必需的。在您自己的源代码中注明出处也值得赞赏,但不是必需的。
文件 ColorButton.cpp 和 ColorButton.h 来自 Tim Smith 的 WTL 带 XP 主题的颜色选择器。它们不受上述许可声明的约束;请参阅这些文件中的注释以了解其许可。
修订历史
2006 年 2 月 8 日:文章首次发表。
系列导航:« 第八部分(属性表和向导)