📄 wtl for mfc programmers, part ix - gdi classes, common dialogs, and utility classes - wtl.htm
字号:
<P>介绍所有的类会使本文变成超级长文章,本文只介绍前两个最经常使用的类。</P>
<H3><A name=usingcfiledialog></A><font color="#FFFF66">CFileDialog</font></H3>
<P><CODE>CFileDialog</CODE>类和<CODE>CFileDialogImpl</CODE>类(译者注:一个是接口类,一个是实现类)用于显示文件打开和保存对话框,<CODE>CFileDialogImpl</CODE>类中最重要的两个成员是<CODE>m_ofn</CODE>
<CODE>和m_szFileName。</CODE><CODE>m_ofn</CODE> <CODE>是一个OPENFILENAME</CODE>结构,<CODE>和MFC一样,CFileDialogImpl</CODE>
使用一些有意义的默认值填充这个结构,如果有必要你可以直接操作这个成员修改其中的属性。<CODE>m_szFileName</CODE> 是一个<CODE>TCHAR</CODE>
数组,用来保存选择的文件名。 (<CODE>CFileDialogImpl</CODE> 只有一个字符串缓冲区保存文件名,如果要选择多个文件需要指定你自己的缓冲区)。
<P>使用<CODE>CFileDialog</CODE> 的基本步骤:</P>
<OL>
<LI>创建一个<CODE>CFileDialog对象,通过构造函数传递一些初始数据。</CODE>
<LI><CODE>调用 DoModal()。</CODE>
<LI><CODE>如果 DoModal()</CODE> 返回<CODE>IDOK,在</CODE><CODE>m_szFileName</CODE>中得到文件名。</LI>
</OL>
<P>下面是<CODE>CFileDialog类的构造函数</CODE>:</P>
<PRE><font color="#0000FF">CFileDialog::CFileDialog (
BOOL bOpenFileDialog,
LPCTSTR lpszDefExt = NULL,
LPCTSTR lpszFileName = NULL,
DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
LPCTSTR lpszFilter = NULL,
HWND hWndParent = NULL )</font></PRE>
<P><CODE>创建打开文件对话框需要指定bOpenFileDialog为true</CODE>(<CODE>CFileDialog将调用</CODE><CODE>GetOpenFileName()</CODE>
显示对话框),创建文件保存对话框需要指定<code>bOpenFileDialog</code>为false(<CODE>CFileDialog</CODE>
<CODE>调用GetSaveFileName()</CODE>)。 其它参数对应着<CODE>m_ofn</CODE>结构中的成员,它们是可选的参数,因为你可以在调用DoModal()之前直接操作<CODE>m_ofn</CODE>修改这些值。</P>
<P>与MFC的<CODE>CFileDialog有一点显著的不同,那就是</CODE><CODE>lpszFilter</CODE>参数必须是用null字符分隔的字符串列表(<CODE>格式和OPENFILENAME</CODE>
文档中说明的一样),而不是用“|”分隔的字符串列表。</P>
<P>下面的例子演示了使用带有filter的<CODE>CFileDialog</CODE>选择 Word 12 文件(<CODE>*.docx</CODE>)(译者注:传说中的office
2007):</P>
<PRE><font color="#0000FF">CString sSelectedFile;
CFileDialog fileDlg ( <SPAN class=cpp-keyword>true</SPAN>, _T(<SPAN class=cpp-string>"docx"), NULL,
OFN_HIDEREADONLY | OFN_FILEMUSTEXIST,
_T("Word 12 Files\0*.docx\0All Files\0*.*\0"</SPAN>) );
<SPAN class=cpp-keyword>if</SPAN> ( IDOK == fileDlg.DoModal() )
sSelectedFile = fileDlg.m_szFileName;</font></PRE>
<P><CODE>CFileDialog</CODE>类对本地化的支持不是很好,那是因为构造函数使用LPCTSTR类型的参数,不仅如此,filter字符串处理起来也很蹩脚。有两个解决方案,一是直接操作<CODE>m_ofn</CODE>,另一个是从<CODE>CFileDialogImpl派生新类。这里我们采用第二种方式,派生一个新类,然后做如下修改:</CODE></P>
<OL>
<LI>构造函数中的字符串参数使用<CODE>_U_STRINGorID</CODE> <CODE>代替LPCTSTR</CODE>。
<LI>和MFC一样,filter 字符串改用“|”分隔,而不是null字符。
<LI>对话框相对于父窗口自动居中。</LI>
</OL>
<P>我们开始编写一个新类,它的构造函数的参数和<CODE>CFileDialogImpl</CODE>的构造函数相似:</P>
<PRE><font color="#0000FF"><SPAN class=cpp-keyword>class</SPAN> CMyFileDialog : <SPAN class=cpp-keyword>public</SPAN> CFileDialogImpl<CMyFileDialog>
{
<SPAN class=cpp-keyword>public</SPAN>:
<SPAN class=cpp-comment>// Construction</SPAN>
CMyFileDialog ( BOOL bOpenFileDialog,
_U_STRINGorID szDefExt = 0U,
_U_STRINGorID szFileName = 0U,
DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
_U_STRINGorID szFilter = 0U,
HWND hwndParent = NULL );
<SPAN class=cpp-keyword>protected</SPAN>:
LPCTSTR PrepFilterString ( CString& sFilter );
CString m_sDefExt, m_sFileName, m_sFilter;
};</font></PRE>
<P>构造函数初始化三个<CODE>CString</CODE> 成员,必要时可能从资源中装载字符串:</P>
<PRE><font color="#0000FF">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)
{
}</font></PRE>
<P>注意一点,这三个字符串在调用基类的构造函数的时候都是空的,这是因为基类的构造函数是在三个字符串初始化之前调用的,要设置<CODE>m_ofn中的字符串数据,我们需要添加一些代码将</CODE><CODE>CFileDialogImpl</CODE>
构造函数中的初始化步骤重做一遍:</P>
<PRE><font color="#0000FF">CMyFileDialog::CMyFileDialog(...)
{
<B> m_ofn.lpstrDefExt = m_sDefExt;
m_ofn.lpstrFilter = PrepFilterString ( m_sFilter );
<SPAN class=cpp-comment>// setup initial file name</SPAN>
<SPAN class=cpp-keyword>if</SPAN> ( !m_sFileName.IsEmpty() )
lstrcpyn ( m_szFileName, m_sFileName, _MAX_PATH );
</B>}</font></PRE>
<P><CODE>PrepFilterString()</CODE> 是一个辅助函数,将的filter字符串中的“|”分隔转换成null字符,结果就是将“|”分隔的filter字符串转换成<CODE>OPENFILENAME所需要的格式。</CODE></P>
<PRE><font color="#0000FF">LPCTSTR CMyFileDialog::PrepFilterString(CString& sFilter)
{
LPTSTR psz = sFilter.GetBuffer(<SPAN class=cpp-literal>0</SPAN>);
LPCTSTR pszRet = psz;
<SPAN class=cpp-keyword>while</SPAN> ( '\<SPAN class=cpp-literal>0</SPAN>' != *psz )
{
<SPAN class=cpp-keyword>if</SPAN> ( '|' == *psz )
*psz++ = '\<SPAN class=cpp-literal>0</SPAN>';
<SPAN class=cpp-keyword>else</SPAN>
psz = CharNext ( psz );
}
<SPAN class=cpp-keyword>return</SPAN> pszRet;
}</font></PRE>
<P>这些转换简化了字符串的处理。要实现窗口的自动居中显示,我们需要重载<CODE>OnInitDone()</CODE>,这需要添加消息映射(这样我们能够链接到基类的通知消息),下面是我们的<CODE>OnInitDone()处理函数:</CODE></P>
<PRE><font color="#0000FF"><SPAN class=cpp-keyword>class</SPAN> CMyFileDialog : <SPAN class=cpp-keyword>public</SPAN> CFileDialogImpl<CMyFileDialog>
{
<SPAN class=cpp-keyword>public</SPAN>:
<SPAN class=cpp-comment>// Construction</SPAN>
CMyFileDialog(...);
<B> <SPAN class=cpp-comment>// Maps</SPAN>
BEGIN_MSG_MAP(CMyFileDialog)
CHAIN_MSG_MAP(CFileDialogImpl<CMyFileDialog>)
END_MSG_MAP()
<SPAN class=cpp-comment>// Overrides</SPAN>
<SPAN class=cpp-keyword>void</SPAN> OnInitDone ( LPOFNOTIFY lpon )
{
GetFileDialogWindow().CenterWindow(lpon->lpOFN->hwndOwner);
}</B>
<SPAN class=cpp-keyword>protected</SPAN>:
LPCTSTR PrepFilterString ( CString& sFilter );
CString m_sDefExt, m_sFileName, m_sFilter;
};</font></PRE>
<P><CODE>关联到CMyFileDialog</CODE> 对象的窗口实际上是文件打开对话框的一个子窗口,因为我们需要窗口队列的顶层窗口,所以调用<CODE>GetFileDialogWindow()得到这个顶层窗口</CODE>。</P>
<H3><A name=usingcfolderdialog></A><font color="#FFFF66">CFolderDialog</font></H3>
CFolderDialog</CODE><CODE>和CFolderDialogImpl类用来显示一个浏览文件夹的对话框,Windows的文件夹浏览对话框能够查看整个外壳名字空间(shell
namespace)的任何位置,但是CFolderDialog,</CODE><code>只支持浏览文件文件系统。</code>CFolderDialogImpl中最重要的两个数据成员是<CODE>m_bi</CODE>
<CODE>和 m_szFolderPath,</CODE><CODE>m_bi</CODE> <CODE>一个 BROWSEINFO</CODE> <CODE>类型的数据结构,它由CFolderDialogImpl</CODE>
负责维护并作为参数传递给<CODE>SHBrowseForFolder()</CODE> API,必要时可以直接修改这个数据结构,<CODE>m_szFolderPath</CODE>
是一个<CODE>TCHAR</CODE> 类型的数组,它存放选中的文件夹全名。</P>
<P><CODE>使用CFolderDialog的步骤是:</CODE></P>
<OL>
<LI><CODE>创建一个CFolderDialog对象,通过构造函数传递初始数据。</CODE>
<LI><CODE>调用 DoModal()。</CODE>
<LI>如果<CODE>DoModal()</CODE> 返回<CODE>IDOK</CODE>,就可以从<CODE>m_szFolderPath获得文件夹名称。</CODE></LI>
</OL>
<P><CODE>下面是CFolderDialog的构造函数:</CODE></P>
<PRE><font color="#0000FF">CFolderDialog::CFolderDialog (
HWND hWndParent = NULL,
LPCTSTR lpstrTitle = NULL,
UINT uFlags = BIF_RETURNONLYFSDIRS )</font></PRE>
<P><CODE>hWndParent</CODE> 是浏览对话框的拥有者窗口,可以通过构造函数在创建时指定拥有者窗口,也可以在调用<CODE>DoModal()</CODE>
时通过这个函数的参数指定拥有者窗口。<CODE>lpstrTitle</CODE> 是显示在浏览窗口中树控件上方的文字标签,<CODE>uFlags</CODE>
是一个标志,它决定了浏览对话框的行为。<CODE>uFlag应该总是包括</CODE><CODE>BIF_RETURNONLYFSDIRS属性,这样树控件就只显示文件系统的目录,有关这个标志的其它情况可以查阅</CODE>关于<CODE>BROWSEINFO</CODE>数据结构的帮助文档,不过有一点需要了解,那就是并不是所有的标志属性都会产生好的作用,比如<CODE>BIF_BROWSEFORPRINTER。不过与UI相关的一些标志工作的很好,比如</CODE><CODE>BIF_USENEWUI。</CODE>还有一点就是构造函数中的<CODE>lpstrTitle参数在使用时会有点小问题。</CODE></P>
<P>下面是使用<CODE>CFolderDialog选择目录的例子:</CODE></P>
<PRE><font color="#0000FF">CString sSelectedDir;
CFolderDialog fldDlg ( NULL, _T(<SPAN class=cpp-string>"Select a dir"</SPAN>),
BIF_RETURNONLYFSDIRS|BIF_NEWDIALOGSTYLE );
<SPAN class=cpp-keyword>if</SPAN> ( IDOK == fldDlg.DoModal() )
sSelectedDir = fldDlg.m_szFolderPath;</font></PRE>
<P><CODE>现面演示一下如何使用定制的CFolderDialog,我们从</CODE><CODE>CFolderDialogImpl类派生一个新类并设置初始选择,由于这个对话框的回调不使用Windows消息</CODE>,所以新类也不需要消息映射链,只需重载<CODE>OnInitialized()函数即可,这个函数在基类接收到</CODE><CODE>BFFM_INITIALIZED</CODE>
通知消息时被调用,<CODE>OnInitialized()调用</CODE><CODE>CFolderDialogImpl::SetSelection()</CODE>
改变对话框的初始选择。</P>
<PRE><font color="#0000FF"><SPAN class=cpp-keyword>class</SPAN> CMyFolderDialog : <SPAN class=cpp-keyword>public</SPAN> CFolderDialogImpl<CMyFolderDialog>
{
<SPAN class=cpp-keyword>public</SPAN>:
<SPAN class=cpp-comment>// Construction</SPAN>
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;
}
<B><SPAN class=cpp-comment>// Overrides</SPAN>
<SPAN class=cpp-keyword>void</SPAN> OnInitialized()
{
<SPAN class=cpp-comment>// Set the initial selection to the Windows dir.</SPAN>
TCHAR szWinDir[MAX_PATH];
GetWindowsDirectā????用????????ory ( szWinDir, MAX_PATH );
SetSelection ( szWinDir );
}</B>
<SPAN class=cpp-keyword>protected</SPAN>:
CString m_sTitle;
};</font></PRE>
<H2><A name=miscstuff></A><font color="#FFFF66">其它有用的类和全局函数</font></H2>
<H3><A name=structwrappers></A><font color="#FFFF66">对结构的封装</font></H3>
<P>和MFC一样,WTL 也对<CODE>SIZE、</CODE><CODE>POINT和</CODE><CODE>RECT</CODE>数据结构进行了封装,分别是<CODE>CSize、</CODE><CODE>CPoint和</CODE><CODE>CRect</CODE>类。</P>
<H3><A name=dualtypedargs></A><font color="#FFFF66">处理双类型参数的类</font></H3>
<P>就像前面提到的那样,你可以使用<CODE>_U_STRINGorID</CODE> 去自适应那些参数是数字或字符串资源ID的函数,WTL中还有两个类和这个类的作用类似:</P>
<UL>
<LI><CODE>_U_MENUorID</CODE>: <CODE>这个类型支持UINT</CODE> <CODE>或 HMENU,通常用在</CODE><CODE>CreateWindow()</CODE>
的封装中函数中,<CODE>hMenu</CODE> 参数在某些情况下是菜单句柄,但是在创建子窗口时它又是一个窗口ID,<CODE>_U_MENUorID</CODE>
用来消除(隐藏)这些差异,<CODE>_U_MENUorID</CODE> 有一个<CODE>m_hMenu成员,用来向</CODE><CODE>CreateWindow()</CODE>
<CODE>或 CreateWindowEx()传递hMenu参数。</CODE>
<LI><CODE>_U_RECT</CODE>: 这个类可以从<CODE>LPRECT</CODE> <CODE>或RECT&</CODE>构建,可以将<CODE>RECT</CODE>
数据结构,<CODE>RECT指针或象</CODE><CODE>CRect那样的封装类转换成很对函数需要的</CODE><CODE>RECT</CODE>类型参数。</LI>
</UL>
<P><CODE>和_U_STRINGorID一样,</CODE><CODE>_U_MENUorID</CODE> <CODE>和_U_RECT</CODE>
也已经随着其它头文件包含在你的工程中了。</P>
<H3><A name=otheā????用????????rclasses></A><font color="#FFFF66">其它工具类</font></H3>
<H4>CString</H4>
<P>WTL<CODE>的CString和</CODE>MFC<CODE>的CString类似,所以这里就不用详细介绍了</CODE>, 不过,WTL<CODE>的CString</CODE>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -