在VC6中实现OFN_EXPLORER风格的文件对话框
1 问题的提出
MFC类库中的CFileDialog类为用户提供了便捷的文件对话框,并且支持Windows 2000和XP中的新EXPLORER风格界面(指定OFN_EXPLORER风格,见图1)。
图1 新EXPLORER风格界面的文件对话框
在VC6中使用CFileDialog,即使指定了OFN_EXPLORER风格,在Windows 2000和XP上运行,也还是老样子(见图2)。可同样的程序,拿到VC2005上编译之后,再运行,却又是新风格了。
图2 旧风格界面的文件对话框
而目前VC6在某些领域的应用还较为广泛,那么为能使得开发的程序界面美观统一,有没有解决办法,使VC6也能显示新风格的文件对话框呢?
2 原因分析
首先,运行的系统必须是Windows 2000以上,才能支持这个界面风格。
基于上述对问题的描述,在VC2005上就能实现,从而将查找原因的方向定位在VC6和VC2005中关于CFileDialog的区别上。
(1)OPENFILENAME定义
查看OPENFILENAME的定义,可以用右键跳转到定义处,或在2005中查看代码定义窗口。
VC2005中OPENFILENAME的定义如下:
typedef struct tagOFNA {
DWORD lStructSize;
……
// 以上同VC6中的定义
#if (_WIN32_WINNT >= 0x0500)
void * pvReserved;
DWORD dwReserved;
DWORD FlagsEx;
#endif // (_WIN32_WINNT >= 0x0500)
} OPENFILENAMEA, *LPOPENFILENAMEA;
可以看出,在Windows 2000以上操作系统中编译(查阅MSDN可知0x0500对应2000系统),就会多出3个变量,lStructSize这个值也就不同了。
操作系统根据这些识别OPENFILENAME版本,从而打开不同的文件对话框。
其实,这就是VC所带的SDK头文件的区别。Windows2000以上操作系统的SDK是支持这个属性的。VC6发行较早,头文件应该还是Windows NT的。
(2)对比CFileDialog::CFileDialog函数
在VC6的该函数处理中,直接指定了OPENFILENAME结构的长度:
memset(&m_ofn, 0, sizeof(m_ofn)); // initialize structure to 0/NULL
……
m_ofn.lStructSize = sizeof(m_ofn);
……
而在VC2005中,检测了操作系统的版本,然后设置不同长度:
// determine size of OPENFILENAME struct if dwSize is zero
if (dwSize == 0)
{
OSVERSIONINFO vi;
ZeroMemory(&vi, sizeof(OSVERSIONINFO));
vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
::GetVersionEx(&vi);
// if running under NT and version is >= 5
if (vi.dwPlatformId == VER_PLATFORM_WIN32_NT && vi.dwMajorVersion >= 5)
dwSize = sizeof(OPENFILENAME);
else
dwSize = OPENFILENAME_SIZE_VERSION_400;
}
// size of OPENFILENAME must be at least version 4
ASSERT(dwSize >= OPENFILENAME_SIZE_VERSION_400);
// allocate memory for OPENFILENAME struct based on size passed in
m_pOFN = static_cast<LPOPENFILENAME>(malloc(dwSize));
ASSERT(m_pOFN != NULL);
if (m_pOFN == NULL)
AfxThrowMemoryException();
memset(&m_ofn, 0, dwSize); // initialize structure to 0/NULL
……
m_ofn.lStructSize = dwSize;
……
在VC2005中,sizeof(OPENFILENAME)已经是新结构的长度了,并且专门定义了旧结构长度的宏定义OPENFILENAME_SIZE_VERSION_400。m_ofn在CFileDialog类中通过__declspec(property(get=GetOFN))声明成了“虚数据成员”,并不存在,实际是通过GetOFN函数访问的新申请的m_pOFN空间。
所以,在VC6中,就没有使用支持OFN_EXPLORER风格的Windows 2000以上系统的OPENFILENAME结构,自然就无法显示新的界面了。
3 解决办法
当然最简单的解决办法是换用2005开发。
如果必须使用VC6,在Codejock Xtreme Toolkit Pro软件中,CXTBrowseEdit类ChooseFile函数提供了一个解决的方法:
// Check to see if this is Windows 2000 or later, if so use the
// Windows 2000 version of OPENFILENAME.
if (XTOSVersionInfo()->IsWin2KOrGreater() && sizeof(OPENFILENAME) < 88 && dlg.m_ofn.lStructSize < 88)
{
// Windows 2000 version of OPENFILENAME has three extra members,
// this was copied from newer version of commdlg.h...
struct OPENFILENAMEEX
{
void* pvReserved; // 4 bytes
DWORD dwReserved; // 4 bytes
DWORD FlagsEx; // 4 bytes
};
// should equal an additional 12 bytes;
dlg.m_ofn.lStructSize = sizeof(OPENFILENAMEEX);
}
这里人为地设置成了新OPENFILENAME结构的长度。按照这个思路,可以在使用CFileDialog类的代码处,通过改进,实现设计目的。
先指定OFN_EXPLORER风格创建CFileDialog对象,然后获取和检测操作系统版本,如果是Windows 2000以上,并且是旧结构的长度,就修改成新的长度。之后再运行,就可以得到新风格的文件对话框了。
实现代码如下:
CFileDialog dlg( TRUE, NULL, NULL,
OFN_EXPLORER,
"所有文件 (*.*)|*.*|" );
OSVERSIONINFO vi;
ZeroMemory(&vi, sizeof(OSVERSIONINFO));
vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
::GetVersionEx(&vi);
if (vi.dwPlatformId == VER_PLATFORM_WIN32_NT && vi.dwMajorVersion >= 5 &&
sizeof(OPENFILENAME) < 88 && dlg.m_ofn.lStructSize < 88)
{
struct OPENFILENAMEEX
{
void* pvReserved; // 4 bytes
DWORD dwReserved; // 4 bytes
DWORD FlagsEx; // 4 bytes
};
dlg.m_ofn.lStructSize = sizeof(OPENFILENAMEEX);
}
dlg.DoModal();
4 结束语
以上代码使用VC6在Windows 2000和XP操作系统上编译运行通过。
读者也可以从CFileDialog派生出新类,在构造函数中增加上面的处理,这样既能实现相同的功能,而且使用更为方便。