Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1559083
  • 博文数量: 884
  • 博客积分: 52280
  • 博客等级: 大将
  • 技术积分: 13060
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-06 09:46
文章分类

全部博文(884)

文章存档

2008年(884)

我的朋友

分类: C/C++

2008-08-06 09:52:58

下载本文示例代码
不久以前,VC知识库曾探讨过如何在MFC应用中打开Windows 2000风格的“文件/打开”对话框(类似Outlook Express左边的Places Bar)。最近又有许多人问及这方面的问题,也许我在那篇文章中讲的不是很清楚,所以这次在本文中重申这个问题。
Windows 2000有个新的“Open”对话框(图一),这个对话框的左边有一个Places Bar,要想让这个对话框出现,必须在::GetOpenFileName函数中传递OPENFILENAME结构,这个结构隐藏在MFC的CFileDialog类中。问题是这个结构最新的版本(即Windows 2000中的5.0及以后的版本)有附加的成员,增加它的尺寸。当你(或MFC)调用::GetOpenFileName之前,你必须初始化结构OPENFILENAME的第一个成员-lStructSize-它决定了OPENFILENAME结构的大小。如果你在Windows 2000(_WIN32_WINNT >= 0x0500)中编译,你会得到结构的最新尺寸;如果你在较早的Windows版本中编译,你却得到旧的结构尺寸。

图一 新的“打开”对话框

当你在Windows 95, Windows 98或Windows NT 4.0运行应用程序的时候,你必须使用旧的结构尺寸。而在Windows 2000中,你既可以使用旧的结构尺寸,也可以使用最新的结构尺寸。如果lStructSize是新的尺寸,Windows 2000打开新风格的对话框。如果lStructSize是旧的尺寸,Windows 2000还检查结构OPENFILENAME中其它的信息:即如果对话框有“钩子”(hook)功能标志(OFN_ENABLEHOOK)或模板标志(OFN_ENABLETEMPLATE),Windows 2000打开旧风格的对话框,否则打开新风格的对话框。这多少使人感觉有些混乱。无论你如何做,MFC42.DLL都被用旧的结构尺寸编译,并且MFC使用一个吊钩过程(OFN_ENABLEHOOK)将对话框连接到你的消息映射......。
天无绝人之路,一位名叫Michael Lemley哥儿们给了我一个问题的解决方案,其基本思路是:在MFC的CFileDialog中增加一个新成员-m_ofnEx,诱使MFC使用这个成员。为了保证代码能在所有的Windows版本中运行,lStructSize 结构成员必须根据实际运行的Windows版本被正确初始化。Michael的方法有两个地方值得商榷:一个是拷贝大量的MFC代码;一个是运行时检查操作系统版本。修改工作是在新类CFileDialogEx中进行的,

// OPENFILENAME 结构的 Windows 2000 版本

struct OPENFILENAMEEX : public OPENFILENAME { 

  void *        pvReserved;

  DWORD         dwReserved;

  DWORD         FlagsEx;

};



// CFileDialog 类的 Windows 2000 版本

class CFileDialogEx : public CFileDialog {

protected:

    OPENFILENAMEEX m_ofnEx; // 新增成员

};
OPENFILENAME 的定义在commdlg.h文件中,而 OPENFILENAMEEX 是模仿 OPENFILENAME 的本地(局部)定义。OPENFILENAMEEX有三个新成员。为了使用这个新结构,CFileDialogEx 重写 DoModal。你必须从CFileDialog 拷贝整个函数到 CFileDialogEx,代码细节如下:
添加 m_ofnEx 



int CFileDialogEx::DoModal()

{

......

    // 前面是 MFC 代码

    

    // 拷贝 m_ofn ==> m_ofnEx 并设置

    memset(&m_ofnEx, 0, sizeof(m_ofnEx));

    memcpy(&m_ofnEx, &m_ofn, sizeof(m_ofn));

    if (IsWin2000())

        m_ofnEx.lStructSize = sizeof(m_ofnEx);



    // 使用 m_ofnEx 运行 "打开"或"保存"对话框

    int nResult;

    if (m_bOpenFileDialog)

        nResult = ::GetOpenFileName((OPENFILENAME*)&m_ofnEx);

    else

        nResult = ::GetSaveFileName((OPENFILENAME*)&m_ofnEx);



    // 回拷 m_ofnEx ==> m_ofn

    memcpy(&m_ofn, &m_ofnEx, sizeof(m_ofn));

   m_ofn.lStructSize = sizeof(m_ofn);

......

}
上述代码的基本思路是在调用 GetOpenFileName 或 GetSaveFileName 之前先将 m_ofn 拷贝到 m_ofnEx,调用完成后再拷回去。其中最重要的是其第一个结构成员----lStructSize 按照实际运行的操作系统版本被初始化。获取操作系统版本参见 IsWin2000 函数:
IsWin2000函数实现

////////////////////////////////////////////////////////////////

// 检测操作系统版本(Windows 2000 或以后)的函数

//



BOOL IsWin2000 () 

{

   OSVERSIONINFOEX osvi;

   BOOL bOsVersionInfoEx;



   // 尝试调用 GetVersionEx 函数,使用 OSVERSIONINFOEX 结构,

   // 它被Windows 2000支持.

   //

   // 如果调用失败, 尝试使用 OSVERSIONINFO 结构.



   ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));

   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);



   if( !(bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi)) )

   {

      // 如果 OSVERSIONINFOEX 不行, 就用 OSVERSIONINFO.



      osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);

      if (! GetVersionEx ( (OSVERSIONINFO *) &osvi) ) 

         return FALSE;

   }



   switch (osvi.dwPlatformId)

   {

      case VER_PLATFORM_WIN32_NT:



         if ( osvi.dwMajorVersion >= 5 )

            return TRUE;



         break;

   }

   return FALSE; 

}
为了让代码正确运行,CFileDialogEx 还重写了另一个虚函数 OnNotify。当在文件对话框中发生某事件时,Windows发送一个 WM_NOTIFY 消息以及 OPENFILENAME 结构中更新的信息。因为该消息处理函数和其它的MFC代码都希望获悉存储在m_ofn中的信息,而不是m_ofnEx中的信息,OnNotify 截获通知并实现另一拷贝。
BOOL CFileDialogEx::OnNotify(...)

{

    memcpy(&m_ofn, &m_ofnEx, sizeof(m_ofn));

    m_ofn.lStructSize = sizeof(m_ofn);

    return CFileDialog::OnNotify( wParam, lParam, pResult);

}
下面是CFileDialogEx类最终的代码:
FileDialogEx 的头文件和实现文件



/////////////////////////////////////////////////////////////

// FileDialogEx.h 

// 

#pragma once



// OPENFILENAME的 Windows 2000 版本.

// 新版本多处三个成员.

// 从commdlg.h文件中拷贝.

struct OPENFILENAMEEX : public OPENFILENAME { 

  void *        pvReserved;

  DWORD         dwReserved;

  DWORD         FlagsEx;

};



///////////////////////////////////////////////////////////////////////////

// CFileDialogEx: 封装 Windows-2000 风格的"打开"对话框.

// 

class CFileDialogEx : public CFileDialog {

      DECLARE_DYNAMIC(CFileDialogEx)

public: 

      CFileDialogEx(BOOL bOpenFileDialog, // TRUE 为"打开", 

                                          // FALSE 为 "另存为"

      LPCTSTR lpszDefExt = NULL,

      LPCTSTR lpszFileName = NULL,

      DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,

      LPCTSTR lpszFilter = NULL,

      CWnd* pParentWnd = NULL);



   // 重载

   virtual int DoModal();



protected:

   OPENFILENAMEEX m_ofnEx; // 新的 OPENFILENAME Windows 2000 版本



   virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult);



   // 处理不同通知消息的虚拟函数

   virtual BOOL OnFileNameOK();

   virtual void OnInitDone();

   virtual void OnFileNameChange();

   virtual void OnFolderChange();

   virtual void OnTypeChange();



   DECLARE_MESSAGE_MAP()

   //{{AFX_MSG(CFileDialogEx)

   //}}AFX_MSG

};





/////////////////////////////////////////////////////////////

// FileDialogEx.cpp 

// 



#include "stdafx.h"

#include 

#include "FileDialogEx.h"



#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif



static BOOL IsWin2000();



///////////////////////////////////////////////////////////////////////////

// CFileDialogEx



IMPLEMENT_DYNAMIC(CFileDialogEx, CFileDialog)



CFileDialogEx::CFileDialogEx(BOOL bOpenFileDialog,

   LPCTSTR lpszDefExt,

   LPCTSTR lpszFileName,

   DWORD dwFlags, LPCTSTR lpszFilter, CWnd* pParentWnd) :

   CFileDialog(bOpenFileDialog, lpszDefExt, lpszFileName,

      dwFlags, lpszFilter, pParentWnd)

{

}



BEGIN_MESSAGE_MAP(CFileDialogEx, CFileDialog)

   //{{AFX_MSG_MAP(CFileDialogEx)

   //}}AFX_MSG_MAP

END_MESSAGE_MAP()



BOOL IsWin2000() 

{

   OSVERSIONINFOEX osvi;

   BOOL bOsVersionInfoEx;



   // 尝试调用 GetVersionEx 函数,使用 OSVERSIONINFOEX 结构,

   // 它被Windows 2000支持.

   //

   // 如果调用失败, 尝试使用 OSVERSIONINFO 结构.



   ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));

   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);



   if( !(bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi)) )

   {

      // 如果 OSVERSIONINFOEX 不行, 就用 OSVERSIONINFO.



      osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);

      if (! GetVersionEx ( (OSVERSIONINFO *) &osvi) ) 

         return FALSE;

   }



   switch (osvi.dwPlatformId)

   {

      case VER_PLATFORM_WIN32_NT:



         if ( osvi.dwMajorVersion >= 5 )

            return TRUE;



         break;

   }

   return FALSE; 

}



int CFileDialogEx::DoModal()

{

   ASSERT_VALID(this);

   ASSERT(m_ofn.Flags & OFN_ENABLEHOOK);

   ASSERT(m_ofn.lpfnHook != NULL); // 仍然是个用户钩



   // 文件缓冲初始化

   ASSERT(AfxIsValidAddress(m_ofn.lpstrFile, m_ofn.nMaxFile));

   DWORD nOffset = lstrlen(m_ofn.lpstrFile) 1;

   ASSERT(nOffset <= m_ofn.nMaxFile);

   memset(m_ofn.lpstrFile nOffset, 0, 

         (m_ofn.nMaxFile-nOffset)*sizeof(TCHAR));



   // WINBUG: 这是一种"打开/保存"对话框的特殊情况,

   //  在它disable主窗口之前是需要处理的.

   //  

   HWND hWndFocus = ::GetFocus();

   BOOL bEnableParent = FALSE;

   m_ofn.hwndOwner = PreModal();

   AfxUnhookWindowCreate();

   if (m_ofn.hwndOwner != NULL && ::IsWindowEnabled(m_ofn.hwndOwner))

   {

      bEnableParent = TRUE;

      ::EnableWindow(m_ofn.hwndOwner, FALSE);

   }



   _AFX_THREAD_STATE* pThreadState = AfxGetThreadState();

   ASSERT(pThreadState->m_pAlternateWndInit == NULL);



   if (m_ofn.Flags & OFN_EXPLORER)

      pThreadState->m_pAlternateWndInit = this;

   else

      AfxHookWindowCreate(this);



   memset(&m_ofnEx, 0, sizeof(m_ofnEx));

   memcpy(&m_ofnEx, &m_ofn, sizeof(m_ofn));

   if (IsWin2000())

      m_ofnEx.lStructSize = sizeof(m_ofnEx);



   int nResult;

   if (m_bOpenFileDialog)

      nResult = ::GetOpenFileName((OPENFILENAME*)&m_ofnEx);

   else

      nResult = ::GetSaveFileName((OPENFILENAME*)&m_ofnEx);



   memcpy(&m_ofn, &m_ofnEx, sizeof(m_ofn));

   m_ofn.lStructSize = sizeof(m_ofn);



   if (nResult)

      ASSERT(pThreadState->m_pAlternateWndInit == NULL);

   pThreadState->m_pAlternateWndInit = NULL;



   // WINBUG: "打开/保存"对话框的特殊情况的第二部分.

   if (bEnableParent)

      ::EnableWindow(m_ofnEx.hwndOwner, TRUE);

   if (::IsWindow(hWndFocus))

      ::SetFocus(hWndFocus);



   PostModal();

   return nResult ? nResult : IDCANCEL;

}



BOOL CFileDialogEx::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)

{

   memcpy(&m_ofn, &m_ofnEx, sizeof(m_ofn));

   m_ofn.lStructSize = sizeof(m_ofn);



   return CFileDialog::OnNotify( wParam, lParam, pResult);

}



////////////////////////////////////////////////////////////////////

// 下列函数只是用来说明他们获得了调用,实际上,类库(MFC)内部的对话框

// 过程是被挂钩的;如果你愿意的话,可以删除他们.

//

BOOL CFileDialogEx::OnFileNameOK()

{

   TRACE(_T("CFileDialogEx::OnFileNameOK\n"));

   return CFileDialog::OnFileNameOK();

}



void CFileDialogEx::OnInitDone()

{

   TRACE(_T("CFileDialogEx::OnInitDone\n"));

   CFileDialog::OnInitDone();

}



void CFileDialogEx::OnFileNameChange()

{

   TRACE(_T("CFileDialogEx::OnFileNameChange\n"));

   CFileDialog::OnFileNameChange();

}



void CFileDialogEx::OnFolderChange()

{

   TRACE(_T("CFileDialogEx::OnFolderChange\n"));

   CFileDialog::OnFolderChange();

}



void CFileDialogEx::OnTypeChange()

{

   TRACE(_T("OnTypeChange(), index = %d\n"), m_ofn.nFilterIndex);

   CFileDialog::OnTypeChange();

}

一旦你用起来以后会发现另外的问题:那就是如何让MFC识别它?如果你深入到MFC中,会发现MFC在一个叫CDocManager::DoPromptFileName的函数中调用文件对话框。幸运的是这个函数是个虚函数,所以你可以重写它,但那需要一个从CDocManager派生一个新类CDocManagerEx,并将它装入你的应用程序。下面就是这个新的派生类:
DocMgrEx头文件和实现文件



////////////////////////////////////////////////////////////////

// DocMgrEx.h 

// 

#pragma once



//////////////////

// CDocManagerEx: 扩展 CDocManager 类使用 CFileOpenEx.

class CDocManagerEx : public CDocManager {

public:

   CDocManagerEx();

   ~CDocManagerEx();



   // 为"打开/保存"对话框重载

   virtual BOOL DoPromptFileName(CString& fileName, UINT nIDSTitle,

       DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate);



protected:

   // 创建"打开"文件对话框的新函数

   virtual CFileDialog* OnCreateFileDialog(BOOL bOpenFileDialog);



   DECLARE_DYNAMIC(CDocManagerEx)

};



////////////////////////////////////////////////////////////////

// DocMgrEx.cpp 

// 

// CDocManagerEx 为了Windows 2000中使用"打开"文件对话框,

// 重写 CDocManager 中的一个函数 DoPromptFileName.

// 使用这个类时, 在你添加任何文档模版之前,添加下列一行代码到应用

// 程序的实例化函数:InitInstance().

//    m_pDocManager = new CDocManagerEx;

// 

#include "stdafx.h"

#include 

#include "DocMgrEx.h"

#include "FileDialogEx.h"



#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif



AFX_STATIC void AFXAPI _AfxAppendFilterSuffix(CString& filter,

   OPENFILENAME& ofn, CDocTemplate* pTemplate, CString* pstrDefaultExt);



IMPLEMENT_DYNAMIC(CDocManagerEx, CDocManagerEx)



CDocManagerEx::CDocManagerEx()

{

}



CDocManagerEx::~CDocManagerEx()

{

}



////////////////////////////////////////////////////////////////////////

// 创建文件对话框的新函数,你可以重载它以创建一些不同类型的CFileDialog,

// 缺省情况下创建 CFileDialogEx.

//

CFileDialog* CDocManagerEx::OnCreateFileDialog(BOOL bOpenFileDialog)

{

   TRACE(_T("CDocManagerEx::OnCreateFileDialog\n"));

   return new CFileDialogEx(bOpenFileDialog);

}



/////////////////////////////////////////////////////////////////////////

// 这个函数是从MFC的 docmgr.cpp文件中考过来的,只对它做了稍微的修改以便

// 使用 CFileDialogEx. 

//

BOOL CDocManagerEx::DoPromptFileName(CString& fileName, UINT nIDSTitle,

   DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate)

{

   // 修改日期 5-15-99 : 调用虚拟函数来创建对话框

   CFileDialog* pDlg = OnCreateFileDialog(bOpenFileDialog);

   ASSERT(pDlg);

   CFileDialog& dlgFile = *pDlg;

   

   CString title;

   VERIFY(title.LoadString(nIDSTitle));



   dlgFile.m_ofn.Flags |= lFlags;



   CString strFilter;

   CString strDefault;

   if (pTemplate != NULL)

   {

      ASSERT_VALID(pTemplate);

      _AfxAppendFilterSuffix(strFilter, dlgFile.m_ofn, pTemplate, &strDefault);

   }

   else

   {

      // 用于所有的文档模板

      POSITION pos = m_templateList.GetHeadPosition();

      BOOL bFirst = TRUE;

      while (pos != NULL)

      {

         CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetNext(pos);

         _AfxAppendFilterSuffix(strFilter, dlgFile.m_ofn, pTemplate,

            bFirst ? &strDefault : NULL);

         bFirst = FALSE;

      }

   }



   // 添加 "*.*" 对全部文件过滤

   CString allFilter;

   VERIFY(allFilter.LoadString(AFX_IDS_ALLFILTER));

   strFilter  = allFilter;

   strFilter  = (TCHAR)''\0'';   // 下一个串

   strFilter  = _T("*.*");

   strFilter  = (TCHAR)''\0'';   // 最后一个串

   dlgFile.m_ofn.nMaxCustFilter  ;



   dlgFile.m_ofn.lpstrFilter = strFilter;

   dlgFile.m_ofn.lpstrTitle = title;

   dlgFile.m_ofn.lpstrFile = fileName.GetBuffer(_MAX_PATH);



   int nResult = dlgFile.DoModal();

   fileName.ReleaseBuffer();



   // 修改日期 5-15-99 : 删除对话框

   delete pDlg;



   return nResult == IDOK;

}



////////////////////////////////////////////////////////////////////////

// 这个函数是原封不动地从MFC的 docmgr.cpp文件中考过来的,因为它是静态函数,

// 所以在这里是必须的.

//

AFX_STATIC void AFXAPI _AfxAppendFilterSuffix(CString& filter, OPENFILENAME& ofn,

   CDocTemplate* pTemplate, CString* pstrDefaultExt)

{

   ASSERT_VALID(pTemplate);

   ASSERT_KINDOF(CDocTemplate, pTemplate);



   CString strFilterExt, strFilterName;

   if (pTemplate->GetDocString(strFilterExt, CDocTemplate::filterExt) &&

    !strFilterExt.IsEmpty() &&

    pTemplate->GetDocString(strFilterName, CDocTemplate::filterName) &&

    !strFilterName.IsEmpty())

   {

      // 添加一个基于文档模板的文件到过滤器

      ASSERT(strFilterExt[0] == ''.'');

      if (pstrDefaultExt != NULL)

      {

         // 设置缺省扩展名

         *pstrDefaultExt = ((LPCTSTR)strFilterExt)   1;  // skip the ''.''

         ofn.lpstrDefExt = (LPTSTR)(LPCTSTR)(*pstrDefaultExt);

         ofn.nFilterIndex = ofn.nMaxCustFilter   1;  // 1 based number

      }



      // 添加过滤器

      filter  = strFilterName;

      ASSERT(!filter.IsEmpty());  // 必须有一个文件类型名

      filter  = (TCHAR)''\0'';  // 下一个串

      filter  = (TCHAR)''*'';

      filter  = strFilterExt;

      filter  = (TCHAR)''\0'';  // 下一个串

      ofn.nMaxCustFilter  ;

   }

}

在程序中使用这个类时,你必须在应用的InitInstance函数中添加一行如下代码:
// 使用扩展的 CDocManager.

// 在创建任何文档模板前添加!

m_pDocManager = new CDocManagerEx;
DocMgrEx.cpp 有一对从MFC拷贝过来的函数,用重新写的 DoPromptFileName 创建 CFileDialogEx,而不是不 CFileDialog。实际上,为了创建对话框,CDocManagerEx 调用一个新的虚函数----OnCreateFileDialog,从而解决了使用 CDocManager 创建对话框产生的问题。也就是说,没有能重写的虚函数来创建不同种类的对话框。真是聪明...... 下载本文示例代码
阅读(196) | 评论(0) | 转发(0) |
0

上一篇:为应用程序添加脚本支持

下一篇: VCKBASE

给主人留下些什么吧!~~