利用MFC的CFileDialog生成Windows2000文件对话框
周鸣扬
自Windows2000推出以后,其新的用户界面使我们有了一次大换口味的机会。比如,其淡入浅出的动画菜单、透明的窗口,另外一个变化就是其文件对话框外观的改变,在程序中打开保存文件时出现的对话框就要比原来在Windows98下面的要“亲热”得多:先看看以下两幅图片:
图一
图二
图一是我们最常见的文件对话框(本文称之为老式的文件对话框),图二是在Windows2000中用得普遍的文件对话框(本文称之为新式的文件对话框)。同样都是文件对话框,却有着不同的界面。在图二中,我们可以看到在对话框的左边有一快捷工具栏,它能够帮助我们在计算机中快速地定位,这在操作上要比图一所示的对话框要方便得多。奇怪的是,图二所示的对话框并非是Windows2000的专利,因为我们在Windows98中运行Office2000时,也能够见到图二所示的文件对话框;在Windows2000中,同样也能够见到图一所示的对话框(如VC的使用界面)。而且,更重要的是,我们使用VC的CFileDialog 所生成的文件对话框,并不能够产生图二所示的界面,这是怎么回事?还有一点不能让人理解的是,有些应用程序(如:“记事本”程序)分别在Windows2000和在Windows98下运行时,居然会有不同的文件对话框界面了?这又是怎么回事?带着这些疑问,我仔细查阅了一下MSDN,对对话框界面有一一点初步的认识。下面将我在编写文件对话框程序中的一点心得写出来,与大家共同探讨:
文件对话框是一种特定的窗口,对话框运行时对各种消息的响应是通过对话的“钩子函数”来完成的。对于对话框的外观,我们可以通过修改“文件对话框模板”来实现;同样,如果要想文件对话实现其它功能(如带文件预览功能的文件对话框),可以通过修改文件对话框的“钩子函数”来完成。在VC中,系统为我们提供了现成的文件对话框类CFileDialog,利用该类,我们可以很方便地生成的文件对话框。在使用CFileDialog时,其中最重要的工作是对其成员变量m_ofn做初始化工作。
m_ofn实际上是一OPENFILENAME类型的结构(Struct) ,OPENFILENAME的定义如下:
typedef struct tagOFN {
DWORD lStructSize;// OPENFILENAME结构的大小
DWORD Flags;
………………此处略去具体的成员变量…………………….
#if (_WIN32_WINNT >= 0x0500)
void * pvReserved;
DWORD dwReserved;
DWORD FlagsEx;
#endif // (_WIN32_WINNT >= 0x0500)
} OPENFILENAME, *LPOPENFILENAME;
由上可以看出,OPENFILENAME中有一Flags成员变量,它决定了对话框的外观,它是一组预定义宏的组合。通过它,我们可以定制个性化的文件对话框。比如,在图一中,“以只读方式打开”这一选项就是因为在Flags中包括了“OFN_READONLY”。
当我们在使用OPENFILENAME结构时,我们已经习惯了用下面的语句来设定lStructSize:
OPENFILENAMEdlgFileOpen;
dlgFileOpen.lStructSize=sizeof(OPENFILENAME);
//注:无论是在Windows98或是Windows2000下运行,lStructSize的值只会是76
实际上,我们如果仔细算一下,76只是#if (_WIN32_WINNT >= 0x0500)前所有成员变量的长度,如果加上#if (_WIN32_WINNT >= 0x0500)以后的三个变量(pvReserved、dwReserved、FlagsEx)的长度, lSstructSize的值应该是76+3*4=88。正是由于这个习惯性的操作,我们才在无意中有意让Windows2000显示老式的文件对话框,原因是什么?请继续往下看:
我们知道,Windows 2000的版本号已经突破了5。如果你使用_WIN32_WINNT = 0x0500进行程序的编译,#if (_WIN32_WINNT >= 0x0500)后的头两个成员参数被当成了保留值,剩下和一个成员参数FlagsEx,就有了一个新的标志值可供选择:OFN_EX_NOPLACESBAR,正是因为OFN_EX_NOPLACESBAR,你才不能够在你所编写的程序中见到快捷工具栏。奇怪的是,和OFN_EX_NOPLACESBAR相比,VC并未提供“OFN_EX_SHOWPLACESBAR”之类的选择。
之所以对lStructSize的值算来算去,是因为在Windows2000中使用MFC的CFileDialog所生成的对话框时,lStructSize的值直接影响着对文件对话框对快捷工具栏显示与否。如果OPENFILENAME的大小是76,则文件对话框不显示快捷工具栏;如果OPENFILENAME的大小是88,文件对话框显示快捷工具栏。当然,这种说法是有一定前提条件的,在以下的叙述中,你会对此有更深刻的了解。
如果Windows 2000 仅仅通过判别OPENFILENAME 的lStructSize值来确定显示文件对话框的方式,那么,为什么有些在Windows98 下运行的程序在Windows 2000下依然能够显示新型的文件对话框(如Windows98中的“记事本”程序)? 很明显,Windows98下的lStructSize值一定是76, 所以Windows 2000一定还有另一种方式来决定使用何种形式来显示文件对话框,是什么呢?
OPENFILENAME结构中有一Flags值,这个值决定着文件对话框的外观。 Windows 2000就是通过这值来从另一方面来决定使用何种形式来显示文件对话框(准确地说,如果你使用的是MFC的CFileDialog所生成的对话框)是同时通过Flags和lStructSize来决定使用何种形式来显示文件对话框。如果Flags值包含了OFN_ENABLEHOOK(启用钩子函数),且lStructSize值是88,显示新的文件对话框;如果Flags不含OFN_ENABLEHOOK(注意:CFileDialog的Flags必须要含OFN_ENABLEHOOK),那么,不管lStructSize的值是多少,显示新的文件对话框;如果OPENFILENAME使用了对话框钩子函数,且lStructSize的值是76, Windows 2000 显示老式的文件对话框;这种显示机制解释了利用MFC的CFileDialog的应用程序在Windows 2000 下运行时总是显示老式的文件对话框。因为, MFC在文件对话框中都使用了对话框钩子函数,不信,你去看看CFileDialog类的实现源程序Dlgfile.cpp第76行:ASSERT(m_ofn.Flags & OFN_ENABLEHOOK),这从根本上说明,MFC的对话框必须使用钩子函数!而且,我们所习惯的“dlgFileOpen.lStructSize=sizeof(OPENFILENAME)”这种操作只会强制地要求Windows2000显示老式的文件对话框。这就注定了使用CFileDialog类的文件对话框在Windows 2000下必定被显示成老式的文件对话框。
问题很明显了,归纳一下上面所述内容,我们可以得出以下结论:1、如果是在Windows98下,你用MFC的CFileDialog类生成的对话框是不能实现新式对话框的显示。也许你会问,Windows98中运行Office2000时,系统照样能够显示新型的文件对话框,笔者在此做猜测,那是因为Office2000的文件对话框根本未使用MFC的对话显示原理,如果非要在Windows98中显示新的文件对话框,你得重新定义一个文件对话框类。2、显然,并不是所有的文件对话框程序都是由MFC的CFileDialog所生成的。如果你不用CFileDialog对话框,你根本不用考虑文件对话框的外观,新产生的对话框外观将自动随操作系统而自动地在新式与老式之间进行转换。3、在Windows 2000下,使用MFC生成的对话框,如果你能够确认文件对话框未使用对话框钩子函数(如GetOpenFileName()函数),那么,你开发的程序一定能够显示出新的对话框(而不必在乎OPENFILENAME中lStructSizer的值),而且这种程序在使用时不必在乎运行的操作系统平台,这也不难理解 “记事本”程序在Windows98和Windows2000下运行时会有不同的文件对话框界面了;如果MFC文件对话框使用钩子函数,那么,有且仅有的办法是修改OPENFILENAME的大小,当OPENFILENAME的大小是88时,Window2000显示新型的文件对话框,否则显示老式的文件对话框。同时要提到的是:将OPENFILENAME的值强行设为88,这样的程序在Windows98下面是不能正常运行的(你根本见不到对话框),你得在程序中加入对应用程序的运行平台的判断代码了。
有了上述的认识,我们也可以利用MFC做出适应系统平台的文件对话框了,正如“写字板”那样:在Windows98下面显示老式的文件对话框,而在Windows2000中,则显示新型的文件对话框。试试下面的程序:
在VC中新建一单文档(SDI)项目,在资源编辑器中分别加入两项菜单项:“用CFileDialog产生对话框”和“用GetOpenFileName产生对话框”,同时加入对该两项菜单COMMAND事件的响应函数,如下:
void CMainFrame::OnCfiledialog()
{
CFileDialog dlgFileOpen(TRUE);
int structsize=0;
DWORD dwVersion,dwWindowsMajorVersion,dwWindowsMinorVersion;
//检测目前的操作系统,GetVersion具体用法详见MSDN
dwVersion = GetVersion();
dwWindowsMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));
dwWindowsMinorVersion = (DWORD)(HIBYTE(LOWORD(dwVersion)));
// 如果运行的操作系统是Windows NT/2000
if (dwVersion < 0x80000000)
structsize =88;//显示新的文件对话框
else
//运行的操作系统Windows 95/98
structsize =76;//显示老的文件对话框
dlgFileOpen.m_ofn.lStructSize=structsize;
TCHAR lpstrFilename[MAX_PATH] = "";
dlgFileOpen.m_ofn.lpstrFile=lpstrFilename;
if(dlgFileOpen.DoModal()==IDOK)
MessageBox("你所打开的文件是:"+(CString)dlgFileOpen.m_ofn.lpstrFile);
else
MessageBox("打开文件出错!");
}
void CMainFrame::OnGetopenfilename()
{
OPENFILENAME ofn;
//设定文件的名称储存空间
TCHAR lpstrFilename[MAX_PATH] = "";
//清空OPENFILENAME
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(OPENFILENAME);//lStructSize的返回值是是76
//设定父窗口
ofn.hwndOwner = this->m_hWnd;
///设定打开文件的类型
ofn.lpstrFilter = "所有的文件\0*.*\0文本文件\0*.TXT\0";
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFile=lpstrFilename;
if (GetOpenFileName(&ofn))
MessageBox("你所打开的文件是:"+(CString)ofn.lpstrFile);
else
MessageBox("打开文件出错!");
}
上述程序在我的网页“国税之家” (http://nationaltax.home.chinaren.com) 中的”个人世界”里可以下载到。如果你还想进一步修改MFC文件对话框并且让你的系统菜单中的“打开\保存”也能够用上新的文件对话框,你可以去重定义CfileDialog类,具体方法本文不做详细说明,不过,你也可以在上面我提到的网站上去下载。
应该说MFC在处理文件对话上面做得不是很好,如果你去查阅MSDN,你是查不到本文所述内容的.一个本来很简单的问题,却做了这么多工作,这也算得上MFC的“特色”吧!