在VC6中实现OFN_EXPLORER风格的文件对话框

来源:岁月联盟 编辑:zhu 时间:2009-03-12

  1 问题的提出

   MFC类库中的CFileDialog类为用户提供了便捷的文件对话框,并且支持Windows 2000和XP中的新EXPLORER风格界面(指定OFN_EXPLORER风格,见图1)。

在VC6中实现OFN_EXPLORER风格的文件对话框

  图1 新EXPLORER风格界面的文件对话框

   在VC6中使用CFileDialog,即使指定了OFN_EXPLORER风格,在Windows 2000和XP上运行,也还是老样子(见图2)。可同样的程序,拿到VC2005上编译之后,再运行,却又是新风格了。

在VC6中实现OFN_EXPLORER风格的文件对话框

  图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派生出新类,在构造函数中增加上面的处理,这样既能实现相同的功能,而且使用更为方便。