1 引言
在windows的程序设计中、为了界面的一致性、微软提供了一组标准的标准对话框,以此方便用户的操作使用,同时也减轻了程序设计人员的工作,使其不必浪费时间去做重复性的工作。但是,在实际的使用中,系统提供的标准对话框并不能满足用户的需要,于是,微软为此提供了让程序设计人员可以在标准对话框的基础上增加新的控件的功能。但这一功能是在SDK层上提供的,而如今大多数开发人员都不用SDK进行开发,而用MFC。我们知道,MFC中有几个类封装了标准对话框。但是在其参考文档中并未指出如何利用这些类实现标准对话框的扩展。经过研究MFC的源代码发现其中封装的几个类是支持标准对话框的扩展的。而且使用起来特别方便简单。本文主要就标准文件对话框(保存和打开)的扩展作以分析。
2 在SDK中的标准文件对话框的实现
在SDK中,由于系统为每一个标准对话框都提供了一个相应的对话框模版资源,所以在使用时我们不需要设计对话框模版资源而直接调用相应的API函数即可。相应于每一种标准对话框,都提供了一个结构,程序员通过此结构来向系统传递初始信息,同时又用此结构来获得用户在标准对话框上所执行的操作。在实际的使用中,为了使用API函数来显示标准对话框,一般都是先要初始化相应的结构的各个字段,并将该结构的指针传递给通用对话库的某个函数,该函数创建并显示对话框。当用户关闭对话框时,被调用的函数将控制返回给程序,程序员可以从传递给它的结构中获取用户在标准对话框上的操作信息。对于标准文件对话框来说,需要使用的结构为OPENFILENAME,其相应的API函数为
BOOL GetOpenFileName( LPOPENFILENAME lpofn ) //针对于“打开”文件对话框
BOOL GetSaveFileName( LPOPENFILENAME lpofn) //针对于“保存”文件对话框
对于标准对话框的扩展正是通过对OPENFILENAME结构的三个成员的设置来实现的,分别为LPOFNHOOKPROC lpfnHook、 LPCTSTR lpTemplateName、DWORD Flags。因此,为了实现对标准对话框的扩展,必须为上面三个成员变量赋以正确的值。三个成员变量的意义如下(其他它成员变量的说明可参见SDK文档):
(1)lpfnHook:指向一个钩子过程的指针。该变量只有当成员变量Flags包含OFN_ENABLEHOOK 标志时才起作用。如果成员变量Flags不包含OFN_EXPLORER标志,则该变量为一个指向OFNHookProcOldStyle钩子过程类型的指针,对话框中所有的消息都发送到该过程,在该过程中,对于所有的标准对话框中的控件的消息,该过程返回false,以便将其发送到标准对话框的默认的处理过程,而对于自己添加的控件的消息则自行处理后返回true。而如果成员变量Flags包含标志OFN_EXPLORER,则该变量为一个指向OFNHookProc钩子过程类型的指针。该过程仅接收程序员增加的控件的消息及其对话框的消息。
(2)lpTemplateName:一个指向以NULL结束的字符串的指针,该字符串标识了一个对话框模版资源,如果对话框模版资源表示为数值型,可以通过宏MAKEINTRESOURCE转化为字符串。该变量仅在成员变量Flags包含标志OFN_ENABLETEMPLATE时有效。如果标志OFN_EXPLORER被设置,则该系统将利用所指定的模版来创建一个对话框,作为默认的标准的Explorer风格的对话框的子窗口。如果OFN_EXPLORER没有被设置,则系统利用指定的模版创建一个old-style的对话框来代替默认的标准对话框。
(3)Flags:可以用来初始化对话框的一系列位标志。为了实现标准对话框的扩展,该成员变量必须包含OFN_ENABLETEMPLATE和OFN_ENABLEHOOK标志。这两个标志分别对应着上面两个成员变量的有效性。
目前,在32位的windows程序中我们主要使用Explorer风格的对话框,因此在此还应该使成员变量Flags包含OFN_EXPLORER标志。使用OFNHookProc类型的钩子过程来处理系统发送来的消息,该过程的原形如下:
UINT_PTR CALLBACK OFNHookProc(
HWND hdlg, // handle to child dialog box
UINT uiMsg, // message identifier
WPARAM wParam, // message parameter
LPARAM lParam // message parameter
);
参数 hdlg:一个代表标准文件对话框的子对话框的句柄,可以以此参数作为GetParent()函数的参数来获得标准文件对话框的句柄。
新增控件的功能的实现就主要在上面的过程中来完成。如前所述,利用SDK进行程序设计非常复杂,所以上面所述只是为充分利用MFC来实现标准对话框的扩展作以基础,因为MFC的源代码正是将上面的所述的功能封装于其中,而文档并未说明,因此下面以上面所述为基础来剖析MFC中的封装并说明在MFC中对标准文件对话框扩展的实现方法。
3 MFC中对标准文件对话框的封装
在MFC中利用类CFileDialog实现了对标准文件对话框的封装,而其是以标准对话框类CComonDialog为基类的,该基类封装了对于所有标准对框来说通用的功能。下面对应着上面的所述的来剖析CFileDialog类的实现。
在CFileDialog类中声明了一个OPENFILENAME结构变量
OPENFILENAME m_ofn;
用来保存结构信息。
同时声明了一个BOOL变量来保存是“打开”还是“保存”文件对话框
BOOL m_bOpenFileDialog;
对于“打开”文件对话框为true,对于“保存”文件对话框为false。
对于OPENFILENAME结构的初始化是在CFileDialog类的构造函数中实现的,相应于上面提到的成员变量的设置如下(没有指定lpTemplateName,该成员变量由程序员在程序中在显示对话框之前提供):
m_ofn.Flags |= dwFlags | OFN_ENABLEHOOK | OFN_EXPLORER;
m_ofn.lpfnHook = (COMMDLGPROC)_AfxCommDlgProc;
由上可以看出,构造函数已经设置了OFN_ENABLEHOOK 和 OFN_EXPLORER两个标志,同时为成员变量lpfnHook指定了一个过程指针。因此程序员在需要对标准文件对话框进行扩展时,仅需设置OFN_ENABLETEMPLATE标志,对成员变量 lpTemplateName设置对话框资源模版标识字符串即可。
对于调用GetOpenFileName()函数来显示对话框的工作封装在CFileDialog类的DoModal函数中,如下:
INT_PTR nResult;
if (m_bOpenFileDialog)
nResult = ::GetOpenFileName(&m_ofn); //“打开”文件对话框
else
nResult = ::GetSaveFileName(&m_ofn); //“保存”文件对话框
下面对于钩子过程_AfxCommDlgProc进行剖析,如上所述,对话框中所有的消息都发送到该过程中,该过程实现了对于这些消息的处理:
_AfxCommDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
//省略一部分
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
If (pThreadState->m_pAlternateWndInit!=NULL&&
Wnd::FromHandlePermanent(hWnd) == NULL)
{
ASSERT_KINDOF(CFileDialog, pThreadState->m_pAlternateWndInit);
pThreadState->m_pAlternateWndInit->SubclassWindow(hWnd);
pThreadState->m_pAlternateWndInit = NULL;
}
//省略一部分
}
由于hWnd代表着用指定的模版资源创建的对话框窗口句柄,而该对话框是默认的标准文件对话框的子窗口,因此,在上面的代码中,将hWnd和从标准对话框派生的对话类的实例相联系起来,即对对话框类中的成员(其实为CWnd的一个成员)m_hWnd赋以hWnd值,以便程序员可以在对话框的成员函数中通过调用GetParent()函数来获的默认的标准文件对话框的窗口对象,从而对标准对话框及其内的标准控件进行操作。同时,对该对话框实现了定制,使对话框中的所有控件的消息的处理可以同一般的对话框内的处理一样。
4 利用CFileDialog类实现对标准文件对话框的扩展
从上面对CFileDialog类的剖析,可以看到,利用CFileDialog实现标准文件对话框得扩展相对来说比较简单些,其主要有两种实现方法:
(1)利用对话框资源编辑器创建一个对话框,其只含有需要额外增加的控件。对话框的属性设置如下:Style:child,因为由该模版创建的对话框将是默认的标准文件对话框的一个子窗口;Border:none,此属性在参考文档中并未要求,事实上,将改属性设置为其它值同样可以工作,但是结果的对话框界面显示有点不太美观,但是同时告诉我们系统如何来结合指定的资源模版和标准资源模版从而生成最终的扩展的对话框,可以不妨一试;WS_CLIPSIBLINGS:true,该属性的设置将确保子对话框不会覆盖在默认的标准对话框的标准控件上。如下两个属性不是必需的:The DS_3DLOOK:true,该属性确保子对话框上的控件和默认的标准对话框上的控件的界面显示的一致性。The DS_CONTROL:true,该属性确保可以利用Tab键在所有的控件(包括子对话框中的和标准对话框中的)之间移动,否则只能作用于默认的标准对话框上的控件。然后利用该模版创建相应的对话框类时,将其基类定为CFileDialog(需要手工改动,因为向导不支持CFileDialog基类,仅支持CDialog)。增加的控件的消息处理同一般的对话框一样,放在此处生成的类中。在对话框显示之前修改OPENFILENAME结构成员,如下为示例代码:
CWzdFileDialog dlg(TRUE, _T(".log"),"", OFN_ALLOWMULTISELECT| OFN_ENABLETEMPLATE |// you will be supplying your own custom dialog box template
0,
szFilter, //file filter
NULL); // parent window
dlg.m_ofn.lpTemplateName=MAKEINTRESOURCE(IDD_WZD_FILEOPEN);
dlg.DoModal();
利用上面所述的方式进行工作,默认的情况下,系统对指定的对话框模版和标准的对话框模版合并的方式为:就下原则,即指定的模版中的所有控件将整体被放在标准对话框的下面,这样对于合理的控件布局产生了一定的影响,进而影响了扩展后的对话框界面的效果。为了满足用户对界面的任意布局的要求,微软为此提供了一种方法:即在设计子对话框的资源模版时,可以在其中加入一个具有特殊标识符的控件,该特殊标识符为stc32(一个预订义的标识符,定义在文件dlgs.h中),文档中指出该控件类型为静态(Static)控件,实际上,由于在系统合并后,将把标准文件对话内的所有控件显示在该控件之内(此时的控件大小将为标准文件对话框内的所有控件组成的布局所占有的空间大小),因此该控件的类型也可以指定为其他类型,影响的只是显示效果,如可指定为Button、GroupBox、Edit等。然后加入其它的控件,其位置根据需要相对于该控件的位置来放置,便可产生需要的界面布局效果。
(2)不需要创建一个对话框资源模版,而是直接产生一个以CFileDialog为基类的对话框类,将实际的对话框扩充工作放在该类的OnInitDialog()函数中(在基类的OnInitDialog()函数调用之后),其主要实现方法如下:首先利用GetParent()函数获得原对话框(即标准文件对话框)的窗口对象CWnd,然后利用的获得对象施加一定的操作,如同一般的窗口一样,扩大窗口的大小,在新的空白区域上创建所需的控件。可在函数OnDestroy中调用相应的函数来获得用户的选定操作信息。如下为一示例代码:
BOOL CFileDialogEx::OnInitDialog()
{
CFileDialog::OnInitDialog();
char szText[120];
const UINT iExtraSize = 50;
CWnd *wndDlg = GetParent(); //此处返回的是标准文件对话框,与一般的对话框不同
//因为该类以CFileDialog为基类,而一般的以CDialog为基类的返回框架窗口的句柄
RECT Rect;
wndDlg->GetWindowRect(&Rect);
wndDlg->SetWindowPos(NULL, 0, 0,
Rect.right - Rect.left,
Rect.bottom - Rect.top + iExtraSize,
SWP_NOMOVE);
CWnd *wndComboCtrl = wndDlg->GetDlgItem(cmb1); //cmb1标准对话框中的代表文件名的组合框的标识符
wndComboCtrl->GetWindowRect(&Rect);
wndDlg->ScreenToClient(&Rect);
Rect.top += 60;
Rect.bottom += 120;
Rect.left += 50;
m_Combo.Create(WS_CHILD | WS_VISIBLE | CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP,
Rect, wndDlg, IDC_MYCOMBOBOX); //由于所创建的控件要以标准对话框为父
//窗口,而该类并不代表标准对话框类,所以此处不能用this,只能用wndDlg
for (int i=0; i < 3; i++) {
sprintf(szText, "Subtype%d", i);
wndDlg->SendDlgItemMessage(IDC_MYCOMBOBOX, CB_INSERTSTRING,
(WPARAM) (-1), (LPARAM) (szText));
if (m_nSubType == i)
wndDlg->SetDlgItemText(IDC_MYCOMBOBOX, szText);
}
return TRUE;
}
void CFileDialogEx::OnDestroy()
{
CFileDialog::OnDestroy();
char szText[40];
// 获得用户选择信息
if (GetParent()->GetDlgItemText(IDC_MYCOMBOBOX, szText, sizeof(szText)) > 0)
m_nSubType = szText[strlen(szText)-1] - '0';
}
显然如上两种方法相较而言第一种方法比较简单方便,但是第二种方法相对来说可以实现对话框中的控件的动态处理。在上面的示例代码中用到了两个标识符cmb1和stc2,它们都是标准文件对话框内的控件的标识符,由于随着版本的不同,他们可能代表着不同的控件,因此如果在需要操作标准文件对话框内的控件时,可以查看当前版本中的标识符所指代的意思。所有标准对话框所用到的标识符的定义包含在文件dlgs.h文件中,而每个标准对话框都有相应的资源模版文件,对于标准文件对话框的资源模版文件为FileOpen.dlg。其它的可参考SDK文档。
5、结束语
本文主要就SDK对标准文件对话框的扩展支持作以简要解释,并通过查看MFC中的对标准文件对话框封装的类CFileDialog的源代码进行剖析,确定了MFC是如何对标准对话框实现支持的,同时说明了如何利用CFileDialog在MFC中来方便的实现对标准文件对话框的扩展。在实际中,许多情况下,微软提供的默认的标准对话框并不能满足我们的特殊需要,因此常需要进行对标准对话框的扩展。
本文解析了Windows SDK对标准文件对话框的扩展支持,并通过MFC中的CFileDialog类源代码剖析,展示了如何方便地扩展标准文件对话框,以满足特定需求。
489

被折叠的 条评论
为什么被折叠?



