65.9K
CodeProject 正在变化。 阅读更多。
Home

在 WinForms 和 WPF 应用程序中托管 MFC MDI 应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (48投票s)

2005年12月30日

CPOL

25分钟阅读

viewsIcon

401141

downloadIcon

14084

在 .NET 应用程序中构建旧的 MFC 应用程序,用现代 WinForms 或 WPF 框架替换 MFC MDI 框架,并将其中的 MFC 视图和对话框集成

包含用于托管简单向导 MFC/对话框应用程序的源代码的编程指南

目录

引言

就 .NET 的开发方式而言,对于一组基于 MFC MDI 架构的应用程序,存在一个将它们迁移到 .NET 的新问题,而且成本高昂。我只找到一个迁移工具 - DudeLabs 的 RC Converter。但它只支持 MFC MDI GUI 的一小部分转换 - 仅限于 MFC 资源。GUI 的大部分和所有业务逻辑,您都必须自己转换。

作为迁移方案的替代方案,Microsoft 提供了一些互操作解决方案(在互操作方案中,您不需要自动将 MFC 代码转换为 .NET,您只需要确保它们可以协同工作)

第一种技术只允许托管 MFC 控件。(“如果您的 Win32 逻辑已经很好地封装为控件……”)您可以付出一些努力将对话框转换为控件 - 但不能是更大的 MFC MDI 应用程序。

MFC Windows 窗体支持类允许将 .NET 代码反向集成到 MFC 应用程序中。它允许您将 .NET 窗体布置为视图或对话框。在这种情况下,存在一些重要的限制:.NET 框架无法集成(必须使用旧的 MFC 框架),集成窗体功能的一部分不可用(例如,布局),并且只能将单独的控件作为窗体集成(例如,.NET MenuStrip 无法在 MFC 框架中使用)。我发现这些类型的互操作解决方案是迁移方案的纯粹替代方案,而且我在互联网上没有找到这些解决方案的真实示例。

针对特定应用程序(Johan Rosengren 的UMLEditor - 重新审视矢量编辑器),本文演示了如何在现有 .NET 应用程序中构建旧的 MFC MDI 应用程序,或为其快速创建新的 WinForms/WPF GUI 框架。在此之后,您可以逐步将旧的 MFC 对话框和视图迁移到新的 WinForms,然后迁移到 WPF UI 元素。与完全迁移到 .NET 相比,这种 MFC 和 WinForms 的中间集成对于大型 MFC MDI 项目可能更为便捷。

从技术角度来看,本文使用的混合 MFC DLL 带有 CWinApp 派生类、在 WinForms 控件中托管 MFC 视图以及在托管 .NET 1.x 和 .NET 2.0 WinForms 控件中支持 WPF 自动布局,这些在互联网上都没有讨论。

这是一个如何通过使用 MfcAdapter 包装器(50 KB!WinForms 和 WPF 版本的源代码)来演进应用程序 GUI 框架的示例

作为原始 MFC MDI 应用程序的 UMLEditor

使用 WinForms 框架构建的 UMLEditor - 带有停靠和皮肤(stardock)的窗体

在 WPF 页面中构建的 UMLEditor(带有两个 UML 图的 FlowDocument

MFC 与 CWinApp 派生类和托管 C++ DLL 混合

通常,基于 MFC 类的应用程序架构是强类型化的。许多类,例如 MDI 类 CDocumentCViewCFrameWnd 等,如果没有 CWinApp(应用程序)类和复杂的初始化就无法使用。在这种情况下,简单的解决方案是将整个应用程序放入 DLL 中,并与托管 C++ 类混合。我们可以将这些类用作 WinForms 框架和 MFC 应用程序之间的托管接口。在这种情况下,我们必须解决“第二个”MFC 应用程序主窗口的问题。它必须是不可见的,并且所有 MFC 窗口都必须托管在 WinForms MainForm 窗口中。但是,在这种情况下,MFC 应用程序的所有功能都可以在托管框架中使用。首先,让我们讨论混合 DLL 的创建。我们为 VS2003 和 VS2005 提供了不同的解决方案,因为混合 DLL 加载过程在每种情况下都不同(混合程序集的初始化)。

VS2003

让我们使用 MSDN 中的标准解决方案(将 C++ 托管扩展项目从纯中间语言转换为混合模式)。请参阅“修改包含使用托管代码和 DLL 导出或托管入口点的使用者的 DLL”这一段。

为了防止在 DLL 初始化期间调用不安全代码(加载器锁定问题),Microsoft 建议使用 _noentry 链接器选项。在这种情况下,混合 DLL 没有入口点,_CRT_INIT 不调用静态 MFC 构造函数,并且 _DllMainCRTStartup 不调用 MFC DllMain(它调用 CWinApp::InitInstance)。只创建和初始化托管对象。

要创建静态 MFC 对象并初始化 MFC 对象,您必须从托管代码中调用 __crt_dll_initialize() 静态函数。您必须小心调用 __crt_dll_initialize() 的位置,并小心您的 MFC 构造函数和 InitInstance 方法代码 - 不要忘记加载器锁定问题,并且不要使用未初始化的 MFC 本机代码!!!

除此之外,您必须在 InitInstance 的开头显式调用 InitApplication,而不是调用 CWinApp::InitInstance()。否则,这将导致 CDocManagerPreTranslateMessage(将短键转换为命令消息)、内存泄漏等问题。

关于混合 MFC 和托管 C++ DLL 的初始化有一些一般性说明。我不会描述 MFC 对象初始化的特殊情况(例如,扩展 DLL 资源链的初始化),因为这不是本文的主题。最后一点是关于终止。您必须小心先关闭 CWinApp 对象(请参阅 MfcAppAdapter.cpp 中的示例代码),然后调用 __crt_dll_terminate()。在这种情况下,WinForms 框架将正确关闭。

现在,我们可以用 UMLEditor(或简单的向导 MFC/对话框应用程序)检查这个解决方案。对于 UMLEditor,进行了以下项目配置更改:

配置类型 动态库 (.dll)
字符集 使用 Unicode 字符集
使用托管扩展
输出文件 .dll
附加选项 /noentry

更新与项目配置文件和其他源文件的编译器选项 /clr 冲突的选项。

基本运行时检查 默认值

创建 MfcAppAdapter.cpp 文件,如下图所示,其中包含一个简单的托管类。使用“单例”模式允许我们封装此类的初始化和从 WinForms 代码中处置。它使 MFC 应用程序能够在首次使用前初始化,并在 WinForms 应用程序退出前终止 MFC 应用程序。此外,MfcAppAdapter 能够封装 MFC 命令消息接口。我们仅将此类用于 DLL 测试(更多安全代码在下载中)

#include "stdafx.h"
#include "_vcclrit.h"

using namespace System;
using namespace System::Windows::Forms;

public __gc class MfcAppAdapter
{
  private:
    static MfcAppAdapter* m_Instance;
    MfcAppAdapter()
    {
      __crt_dll_initialize();
    
      Application::add_ApplicationExit(
          new EventHandler(this, 
             &MfcAppAdapter::OnApplicationExit));
    }

    void OnApplicationExit(Object* sender, EventArgs* e)
    {
      AFX_MANAGE_STATE(AfxGetStaticModuleState());
      CMDIFrameWnd* pMainFrame = 
          dynamic_cast <CMDIFrameWnd*>(AfxGetMainWnd());
      ::SendMessage(pMainFrame->GetSafeHwnd(),WM_CLOSE,0,0);

      //change DLL flag in AFX_MODULE_STATE to careful call 
      //ExitInstance as for application context
      AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
      pModuleState->m_bDLL = NULL;
 
      __crt_dll_terminate();
      m_Instance=NULL;
    }

  public:
    static MfcAppAdapter* GetInstance()
    {
      if(m_Instance==NULL)
      {
        m_Instance = new MfcAppAdapter();
      }
      return m_Instance;
    }

    bool OnCmdMsg( int nID)
    {
      AFX_MANAGE_STATE(AfxGetStaticModuleState());

      CMDIFrameWnd* pMainFrame = 
          dynamic_cast <CMDIFrameWnd*>(AfxGetMainWnd());
      return pMainFrame->OnCmdMsg(nID, 0, NULL, NULL) != 
                                                   FALSE;
    }
};

UMLEditor 不需要特殊初始化,所以我们只在 UMLEditorDemo.cpp 中的 UMLEditorDemo::InitInstance 中添加 InitApplication()。(对于其他应用程序,需要一些小的更改。)

构建 DLL 后,我们可以在一个简单的 C# WinForms 应用程序中托管并测试它(见图)

public Form1()
{
   ...
   private void button1_Click(object sender, 
                              System.EventArgs e)
   {
        //ID_APP_ABOUT
        MfcAppAdapter.GetInstance().OnCmdMsg(0xE140);
   }
}

我们使用按钮单击将 ID_APP_ABOUT 命令消息发送到 UMLEditor

VS2005

如上所述,VS2003 中只执行托管初始化。我们在托管初始化之后自己进行非托管初始化。在 VS2005 中,非托管和托管初始化都执行 - 非托管初始化首先发生,之后发生托管初始化。加载器锁定仍然可能发生(例如,如果 DllMain 中的代码访问 CLR),但现在它确定性地发生并被检测到。不要试图在 VS2005 中使用 /noentry 链接器选项跳过非托管初始化并调用 __crt_dll_initialize()!!!

为了确保 `DllMain` 中的代码和静态变量构造函数不访问 CLR,新的混合 DLL 加载过程需要特殊的项目配置。在以 `DllMain` 为根的调用树中,任何函数都不能编译为 MSIL,因为这意味着访问未初始化的 CLR。我们可以在这里解决问题,包含这些函数的目标文件应该在没有 `/clr` 的情况下编译。在我们的例子中,我们在项目属性中设置“**无公共语言运行时支持**”,并在每个包含托管类的文件的属性中设置“**公共语言运行时支持 (`/clr`)**”。如果 `DllMain` 尝试直接执行 MSIL,将导致编译器警告(级别 1)C4747。但是,编译器无法检测 `DllMain` 调用另一个模块中的函数,而该函数又尝试执行 MSIL 的情况。初始化代码中包含 MSIL 指令的静态变量必须放置在托管模块中,并且不应由非托管初始化进行初始化和使用。

然后,我们必须解决链接器问题 - LNK2005 错误。Microsoft 不支持 MFC 扩展 DLL 中的 CWinApp 派生类 - 所以可能这不是一个链接器错误。无论如何,如果 CRT 库中的 DllMain 在 MFC 库中的 DllMain 之前链接,则会出现链接错误:“mfcs80ud.lib(dllmodul.obj) : error LNK2005: _DllMain@12 already defined in msvcrtd.lib(dllmain.obj)”。我通过一个小技巧解决了这个问题:在静态构造函数或 InitInstance 的开头添加宏 AFX_MANAGE_STATE (AfxGetStaticModuleState())

InitApplication 的问题与 VS2003 中的解决方案相同。

我们不需要调用特殊函数来创建和初始化静态 MFC 对象,也不需要调用终止函数!但我们必须关闭 MFC 应用程序(使用与 VS2003 中相同的代码)。

与 VS2003 不同,如果 MFC 应用程序在 WinForms 主窗体激活后激活 MainFrame 窗口,则托管调试助手会抛出 LoaderLock 异常。但正如稍后将讨论的,我们不需要两个激活的主窗口,因此我们不激活 MFC MainFrame 窗口。

我们用新的 C++/CLI 编写混合 DLL 中的所有托管类。使用“C++ 托管扩展语法升级清单”来迁移托管 VC++ 到 C++/CLI,或者使用 VS2003 MC++。在这种情况下,只需设置项目选项 /clr:oldSyntax 而不是 /clr。所有其他项目设置将与 C++/CLI 相同。

现在,我们可以用 UMLEditor(或简单的向导 MFC/对话框应用程序)检查这个解决方案。

在 VS2005 中自动更新 UMLEditor 后,对项目配置进行以下更改:

配置类型 动态库 (.dll)
字符集 使用 Unicode 字符集
使用托管扩展
输出文件 .dll

更新与项目配置文件和其他源文件的编译器选项 /clr 冲突的选项

基本运行时检查 默认值
启用最小重新生成
启用 C++ 异常 使用 SHE 异常 (/EHa)
调试信息格式 程序数据库 (/Zi)
创建/使用预编译头 不使用预编译头
入口点  

创建 MfcAppAdapter.cpp 文件,如下图所示,其中包含一个简单的托管类。我们从 VS2003 版本中移除了 __crt_dll_initialize()__crt_dll_terminate(),并使用了新的 C++/CLI 语法。在 MfcAppAdapter.cpp 文件属性中设置 CLR 支持

使用公共语言运行时支持编译 公共语言运行时支持 (/clr)
#include "stdafx.h"
using namespace System;
using namespace System::Windows::Forms;

public ref class MfcAppAdapter
{
  private:
    static MfcAppAdapter^ m_Instance;
    MfcAppAdapter()
    {
      Application::ApplicationExit += 
        gcnew EventHandler(this, 
           &MfcAppAdapter::OnApplicationExit);
    }

    void OnApplicationExit(Object^ sender, EventArgs^ e)
    {
      AFX_MANAGE_STATE(AfxGetStaticModuleState());

      CMDIFrameWnd* pMainFrame = 
          dynamic_cast <CMDIFrameWnd*>(AfxGetMainWnd());

      ::SendMessage(pMainFrame->GetSafeHwnd(),WM_CLOSE,0,0);

      //change DLL flag in AFX_MODULE_STATE to careful call 
      //ExitInstance as for application context
      AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
      pModuleState->m_bDLL = NULL;

      delete m_Instance;

      m_Instance=nullptr;
    } 

  public:
    static MfcAppAdapter^ GetInstance()
    {
      if(m_Instance==nullptr)
      {
        m_Instance = gcnew MfcAppAdapter();
      }
      return m_Instance;
    }

    bool OnCmdMsg( int nID)
    {
      AFX_MANAGE_STATE(AfxGetStaticModuleState());

      CMDIFrameWnd* pMainFrame = 
          dynamic_cast <CMDIFrameWnd*>(AfxGetMainWnd());

      return pMainFrame->OnCmdMsg(nID, 0, NULL, NULL) != FALSE;
    }
};

UMLEditorDemo.cppUMLEditorDemo::InitInstance 中添加 InitApplication()。构建 DLL 并在简单的 WinForms 2005 应用程序中测试。

MfcAdapter 的架构

我们到哪里了?到目前为止,我们只讨论了创建混合 MFC 和具有 CWinApp 派生类及托管 C++ DLL 的技术问题。但是,我们如何在 WinForms 或 WPF 框架窗口中托管 MFC 窗口呢?让我们讨论 MfcAdapter 包装器的架构

MfcAdapter 是一个托管组件,由 MfcAppAdapter 和合适的 WndManager 组成

  • MfcAppAdapter (C++/CLI) 是一个非托管传统 MFC 应用程序的包装器。它是 MfcHost 的核心。其主要任务是封装 MFC 应用程序的初始化、该应用程序的命令接口及其窗口托管。命令接口是指传统 MFC 应用程序中命令处理程序的接口。为了访问框架,首先,为了将 MFC 窗口附加到框架,MfcAppAdapter 使用外部 IWndManager 接口。这个解决方案允许我们只为任何框架类型使用一个 MfcAppAdapterMfcAppAdapter 类只使用传统 MFC 应用程序中基于 MFC 的类,因此它独立于 UMLEditor 的特定类,可用于包装其他应用程序。
  • WndManager 是框架的一个外观,它允许我们将 MfcAppAdapter 实现为所用框架的一个组件 - MDI 窗体演示的 WinForms 组件,或 WPF 页面演示的 WPF FrameworkElement。对于其他托管框架,可以实现合适的 WndManagerWndManager 的共同部分是 IWndManager 接口。WndManager 为特定框架实现此接口。WndManager 的其余部分取决于框架以及我们需要的 MFC 应用程序功能。例如,MdiFormsWndManager 实现了 MFC 应用程序中框架的 ToolStripMenuItemsToolStripButtons(VS2003 的 MenuItemToolBarButton)的处理和更新,并支持设计时的 Visual Studio 设计器。WpfPageWndManager 实现了 OpenDocument 方法,并支持 WPF 自动布局管理器。在本文中,我们将讨论 MfcAppAdapterWpfPageWndManagerMdiFormsWndManager 在我们的案例中是辅助性的,本文将不讨论,但您可以使用源代码。

在 WinForms 应用程序中托管 MFC 视图和对话框

我们在这里讨论 MfcAppAdapter 实现中最重要的问题。请使用源代码进行详细理解。

封装的 MFC 代码的更改

在使用创建的混合 DLL 之前,我们必须解决一些问题。

Unicode 集

Unicode 字符集,以简化字符串消息编组。

托管对话框、MFC 主窗口的创建和激活 (UmlEditorDemo.cpp)

MFC 对话框窗口使用传统 MFC 应用程序的主窗口作为默认父窗口。如果我们将 WinForms 框架 MainForm 窗口设置为传统 MFC 应用程序主窗口的父级,我们可以保留托管对话框的模态属性。我们可以通过以下解决方案解决此问题:

  1. 在我们的应用程序类中添加 m_ExtFramework 成员、AttachApplication 方法和 AttachApplication 静态函数。m_ExtFramework 保存了 WinForms 框架的接口 (IFramework)。AttachApplication 方法是 m_ExtFramework 的设置器,而 AttachApplication 静态函数是访问 AttachApplication 方法的简单接口(我们使包装器类 MfcAppAdapterViewCtrl 独立于 MFC 应用程序)。WinForms 框架在混合 DLL 初始化后但在使用任何传统 MFC 应用程序之前调用 AttachApplication 方法。
  2. 将主窗口的创建从 InitInstance 移到 AttachApplication 方法。
  3. 覆盖 CMainFrame::PreCreateWindow,并在 cs.hwndParent 中设置来自 IFramework 的 MainForm 窗口句柄。
  4. 通过关闭 MainForm 窗口,子窗口被销毁。传统的主窗口接收 WM_DESTROY 消息。在这种情况下,我们必须小心关闭传统的 MFC 应用程序。但正常的关闭场景发生在客户端对 MfcAppAdapter 调用 dispose 并在 WinForms Framework MainForm 关闭之前关闭传统的 MFC 应用程序时。因此,如果客户端忘记了这一点,我们会在输出窗口中写入警告。
  5. .NET 1.x 和 .NET 2.0 的消息循环实现不同。在 .NET 1.x 中,消息循环首先在 MainForm 中预处理来自无模态对话框控件的一些常见控件消息(WM_NOTIFYWM_COMMAND)。我们通过在传统 MFC 应用程序中显式预翻译这些消息来解决此问题。

我们还使用 CNotifyHook 作为 COleFrameHook 接口实现,以使 WinForms 框架窗口与传统 MFC 应用程序窗口同步。

我们保持传统 MFC 应用程序主窗口不可见。因此,必须从应用程序 InitInstance 中删除一些源代码。

//// The main window has been initialized, so show and update it. 
//pMainFrame->ShowWindow( m_nCmdShow );
//pMainFrame->UpdateWindow();

命令行处理现在在 WinForms 框架中。所以,也删除此代码

//// Dispatch commands specified on the command line
// if (!ProcessShellCommand( cmdInfo ))
//     return FALSE;
托管视图

为了在 WinForms 控件中托管 MFC 视图,我们必须更改视图类和相关类

  1. 首先,视图内容在创建和初始化时必须不可见。它只会在附加到 WinForms 控件后激活。在源代码中,我们在重写的 MultiDocTemplate::OpenDocumentFileCDocManagerEx::OpenDocumentFileCMainFrame::OnWindowNew 中将 bMakeVisibleparameter 设置为 false
  2. 我们必须保存指向最后一个打开文档的指针,我们将在 WinForms 控件中使用它。这是通过我们的 IFramework 接口的 SetOpenedDocument 方法实现的。
  3. 不要使用 CView::OnCreate。创建和初始化继承自 CWndControlDropTarget.RegisterToolTip 等的视图类成员应从 OnCreate 方法移至 OnInitialUpdate。在这种情况下,这些成员将在视图附加到 WinForms 控件后创建。
  4. 视图由 WndManager 附加到 WinForms 控件后,CView::GetParent 方法将返回 WinForms 控件的父 hWnd。因此,所有使用 CView::GetParent 的代码,例如 SetTitle,都必须更改为我们 IFrameworkGetNativeParent 方法。我们为 CDocManagerEx::OpenDocumentFileCUMLEditorDemoDoc::CanCloseFrameCUMLEditorDemoDoc::UpdateFrameCounts 做了这些更改。通过关闭控件容器,WndManager 必须释放带有附加视图的控件!!!在这种情况下,视图将被分离,视图的 MFC CFrame 父级将被恢复,然后视图将在 MFC 应用程序中关闭。我们必须检查所有视图是否已从 WinForms 控件分离,然后才能关闭 MFC 应用程序。UMLEditorDemo::DetachApplication 方法执行此检查。
  5. 为了支持自动布局,我们必须在托管视图中实现一些方法:AutoSizeGetPreferredSizeScaleControl。我们使用带有这些方法的基类 CLayoutView 作为托管视图中 MfcAppAdapter 的接口。视图必须继承此基类。在控件中使用的视图的所有自定义属性都必须以这种方式实现。
  6. 在本文中,我们不描述继承自控件和带有 m_dropTarget(支持拖放)的视图的视图类的更改,因为 UmlEditor 没有这些视图。

包装器类

MfcAppAdapter

传统 MFC 应用程序的命令接口由两个方法包装:OnCmdMsg (CCmdTarget::OnCmdMsg) 和 OnUpdateCmdMsg。我们还使用 CCmdUIHandler 类进行用户界面更新。该类为框架菜单项和工具栏按钮实现了 CCmdUI 接口。GetMfcMenuString 方法实现了对 MFC CMenu 的访问,以支持 WinForms 菜单中的“Recent”菜单项。

在 VS2003 版本中,TranslateMsg 方法支持通用控件的消息调度。

MfcAppAdapter 是一个单例。CreateInstance 方法可用于访问 private static 实例。MfcAppAdapter 构造函数调用 UMLEditorDemo::AttachApplication 来初始化传统的 MFC 应用程序。MfcAppAdapterDispose 检查所有托管视图是否已关闭,然后使用 UMLEditorDemo::DetachApplication 方法关闭传统应用程序。

ViewCtrl

ViewCtrl 支持在 WinForms 控件中托管 MFC 视图。它是 MfcAppAdapter 的辅助类。

  1. 作为 WinForms 控件的子控件,ViewCtrl 继承了 Control 接口。为了托管视图,我们必须将 CView 对象从“CView”窗口分离,并通过创建“ViewCtrl”窗口将 CView 对象附加到“ViewCtrl”窗口。
  2. 我们通过用 StubWndProc 替换 CViewWndProc 来保持从“CView”窗口到 CView 对象的调度消息。StubWndProc 将这些消息从“CView”窗口调度到“ViewCtrl”窗口,然后调度到 CView 对象。
  3. 为了保持“CView”窗口的 CWnd 接口,我们必须将 StubWnd(基于 CWnd)附加到“CView”窗口。现在,我们的 CView 对象连接到“ViewCtrl”窗口和“CView”窗口!这并不糟糕。见图

  4. 通过更改重写的 ViewCtrl::CreateParams 中的“Windows 类”,我们可以在创建“ViewCtrl”窗口时执行这些操作。然后,我们使用 WM_CREATE 消息在 ViewCtrlWindowProc 中调用 ViewCtrl::AttachView 方法。当 CView 对象附加到“ViewCtrl”窗口时,我们调用 CView::OnInitialUpdate;并且视图内容呈现在 WinForms 窗体中,而不是 MFC CFrame 中。
  5. 我们必须将 ViewCtrl 框架(WinForms 窗体)与 MFC CFrame 同步,以支持传统 MFC 应用程序的 MDI 逻辑。我们通过以下方法实现此目的:
    • OnGotFocus
    • ProcessDialogKey
    • SetText
    • GetParent
  6. 重写 OnHandleDestroyed 方法将视图从控件分离到 MFC CFrame,并使用 dispose 关闭视图。
  7. 为了支持自动布局,基控件(VS2005)或特殊基类 LayoutControl(VS2003)的 AutoSizeGetPreferredSizeScaleControl 方法被重写。通过 CLaoutView 接口连接的托管视图实现了这些方法所需的功能。
  8. MfcAppAdapter 使用 IWndManager 接口将创建的 ViewCtrl 控件附加到外部框架,并在控件收到 GotFocus 事件时激活主窗口。否则,外部框架会获取 MfcAppAdapterOnClosing 取消事件处理程序。框架可以使用此处理程序来支持传统应用程序的“CanClose”功能。在 WinForms 框架中,我们只需将 OnClosing 取消事件处理程序与 MainForm 的 Closing 事件连接起来。
UmlEditorCommand

UmlEditorCommand 枚举包含所有可在传统 MFC 应用程序中处理的命令 ID。

WPF 托管 WinForms 控件中的自动布局

WPF 类 WindowsFormsHost 允许我们将 MfcAppAdapter 实现为 WPF FrameworkElement。此外,VS2005 Visual Designer 不支持 WPF,这使得 MfcAdapter 和 WFP 框架的集成更加容易!唯一需要解决的问题是当前版本的 WindowsFormsHost 不支持 WPF 自动布局管理器。在这个简单的 WPF WndManager 版本中,我们展示了如何实现它。

UmlEditorHost

UmlEditorHost 是一个扩展的 WindowsFormsHost,具有对托管控件的自动布局支持。标准解决方案涉及重写 WindowsFormsHost 基方法:MeasureOverrideArrangeOverride

MeasureOverride 中,我们从托管控件获取首选大小(Control.GetPreferredSize 方法),并在必要时保持宽度/高度比不变地将其减小到所需大小。

ArrangeOverride 中,我们将托管控件缩放到首选大小,如果需要则将其缩小到最终大小,并乘以缩放因子。缩放因子保存在添加的依赖属性 Zoom 中,该属性可以与外部布局管理器绑定。

WpfPageWndManager

WpfPageWndManagerIWndManager 接口的 WPF 实现。在 AttachCtrl 方法中,我们只需将由 MfcAppAdapter 创建的控件添加到 UmlEditorHost。在我们的案例中,我们不编辑托管的 UML 图,因此我们禁用托管控件并且不使用其 OnClosing 事件处理程序。

WpfPageWndManager 负责创建和处置 MfcAppAdapter 以及由 MfcAppAdapter 创建的其他控件。它是一个单例,并在其自己的构造函数中创建 MfcAppAdapter 实例。在 DisposeContainer 中,它保存所有带有创建控件的主机。使用 Application.Current.MainWindow.Unloaded 事件,我们处置 DisposeContainer,然后处置 MfcAppAdapter

.NET 2.0 控件布局适配器到 .NET 1.x 控件

如果 MfcAppAdapter 是 .NET 1.x 组件,则我们需要在 WpfPageWndManagerMfcAppAdapter 之间使用 MfcHost1Adapter。此解决方案的原因是 .NET 1.x WinForms 控件没有新的布局方法 AutoSizeGetPreferredSizeScaleControl,并且不能直接支持 WPF 自动布局。通过使用复合控件 .NET 2.0 LayoutControlAdapter 作为容器和 .NET 1.x LayoutControl 作为构成控件,MfcHost1Adapter 允许我们将 .NET 1.x WinForms 控件作为 WpfPageWndManager 的 .NET 2.0 WinForms 控件实现。LayoutControlAdapter 的标准方法:AutoSizeGetPreferredSizeScaleControl 将被包含的 LayoutControl 的自定义方法覆盖。LayoutControlAdapter 可以在其他项目中用于在 .NET 2.0 框架中托管 .NET 1.x 控件。

扩展的 MfcAppAdapterWndManagerAdapter 允许我们将附加的 LayoutControl 替换为复合 LayoutControlAdapter

演示框架

您可以自己创建简单的演示应用程序,也可以使用下载的演示。尝试使用其他托管框架而不是这些演示(这更有趣)。在这里,我将描述两个演示应用程序:MDI 窗体演示和 WPF 页面演示。

MDI 窗体演示

创建 C# Windows 应用程序项目,并在主窗体中添加 ToolStripMenuItemToolStripButton(VS2003 的 MenuItemToolBarButton)的集合。将 MdiFormsWndManager.dll 添加到工具箱。然后,将 UmlEditorComponent 从工具箱拖到主窗体。现在,ToolStripMenuItemsToolStripButtons 具有 CommandName 扩展属性,您可以在其中为连接的 UmlEditorComponent 从列表框中设置合适的命令,如下图所示。只需编译并运行应用程序

WPF 页面演示

创建一个 WinFX Windows 应用程序。添加这些引用 - MfcAppAdapter.dllWpfPageWndManager.dllWindowsFormsIntegration.dll。如果您使用 VS2003 版本的 MfcAppAdapter.dll,也添加 MfcHost1Adapter.dll

在图中所示的窗口中创建一个页面。我们有一个简单的 FlowDocument,其中包含 AS:UMLEditorHostFrameworkElementAS:UMLEditorHostFileSource 属性定义了要添加到文档中的 UML 图的路径。为了支持 SinglePageViewer 缩放,AS:UMLEditorHostZoom 属性与 SinglePageViewer 资源中的 SinglePageViewer.Zoom 绑定。只需编译并运行应用程序

<!--WPF Page Demo-->
<?Mapping XmlNamespace="AS" ClrNamespace="AS.MfcHost2" 
                            Assembly="WpfPageWndManager"?>
<Page xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
      xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
      xmlns:AS="AS" Width="800" Height="540">
    <SinglePageViewer Name="ZoomSource">
        <!--Bind AS:UMLEditorHost.Zoom and SinglePageViewer.Zoom 
            for the Zoom support-->
        <SinglePageViewer.Resources>
            <Style TargetType="{x:Type AS:UMLEditorHost}">
                 <Setter Property="Zoom" 
                         Value="{Binding ElementName=ZoomSource, 
                                        Path=Zoom, Mode=OneWay}"/>
            </Style>
        </SinglePageViewer.Resources>
        <FlowDocument TextAlignment="Left" Background="AliceBlue">
            <Paragraph>
                <Bold>Hosting MFC application in WPF Page</Bold>
            </Paragraph>
            <Paragraph KeepTogether="True">
                Document - <Italic>UMLEDI1.uml</Italic>
                <!--use hosted MFC application as FrameworkElement-->
                <AS:UMLEditorHost FileSource="../../UMLEDI1.uml"/>
            </Paragraph>
            <Paragraph KeepTogether="True">
                Document - <Italic>UMLEDI2.uml</Italic>
                <!--use hosted MFC application as FrameworkElement-->
                <AS:UMLEditorHost FileSource="../../UMLEDI2.uml"/>
            </Paragraph>
        </FlowDocument>
    </SinglePageViewer>
</Page>

部署和安装

请参阅“MfcAdapter - 不是 OLE .Net/WPF 的 MFC 应用程序容器”中的“部署和安装”。

  1. 托管的简单向导 MFC/对话框应用程序

    VS 2005 和 VS2010 编程指南和源代码

    我提供了一个简单的向导 MFC/对话框应用程序 DialogWiz 的编程指南和源代码,作为您托管 MFC 应用程序的起点。为简单起见,我从 MfcAppAdapter 组件中删除了托管 MFC 视图支持。

    只需按照编程指南中的说明更改您的 MFC 应用程序即可。使用 MfcAppAdapter.OnCmdMsg(),您可以将命令从 .NET 窗体发送到您的 MFC 命令处理程序并显示您的对话框。

    代码兼容 VS 2003-VS 2012,您只需更新项目设置即可。

  2. MfcAdapter VS2003\VS2005 托管 UMLEditor

    源代码

    解压后,我们有两个解决方案 - UMLEditor2003 (VS2003) 和 UMLEditor2005 (VS2005)。传统的 MFC 源代码 (Legacy 目录) 对这两个解决方案都是通用的。请不要将 UMLEditor2003 解决方案转换为 VS2005 或将 UMLEditor2005 解决方案转换为 VS2010 - 在这种情况下您需要更改一些源代码。

    UMLEditor2003 解决方案包含 MfcAppAdapterMdiFormsWndManagerMdiFormsDemo 项目。将 MdiFormsDemo 设置为启动项目,然后构建并运行。

    UMLEditor2005 解决方案包含额外的 WpfPageWndManagerWpfPageDemoMfcHost1Adapter 项目。WpfPageDemoWpfPageWndManager 与 WinFX Runtime Components 3.0 - Beta 2 (Jan CTP 程序集,版本 6.0.5070.0) 兼容。

    检查对 WindowsFormsIntegration.dll 的引用(通常是 ..\Program Files\Reference Assemblies\Microsoft\WPF\v3.0\WindowsFormsIntegration.dll)。将 MdiFormsDemoWpfPageDemo 设置为启动项目,然后构建并运行。

    如果您想将 MfcAppAdapter 的 VS2003 版本与 WPF 一起使用,请在 MfcHost1Adapter 中添加对 VS2003 MfcAppAdapter.dll 的引用。构建 MfcHost1Adapter,并在 WpfPageWndManagerWpfPageDemo 中添加对 VS2003 MfcAppAdapter.dllMfcHost1Adapter.dll 的引用,而不是 VS2005 MfcAppAdapter.dll。重新构建并运行。

    我试图使封装的 MFC 代码尽可能简洁,因此我没有修复此代码的一些原生问题,例如,打开不存在的最近文件时未处理的异常。

  3. MfcAdapter 3.0。演示 VS2019

MfcAdapter 3.0 不需要特殊安装。从存档中提取后,您将获得 5 个 MFC 应用程序的源代码和可执行程序

  • DialogWiz” - 使用 VS 2005 向导创建的没有视图(只有对话框)的简单 MFC 应用程序
  • DialogEditor” - Johan Rosengren 的 DIY 矢量和对话框编辑器 (https://codeproject.org.cn/)
  • UMLEditor” - Johan Rosengren 重新审视矢量编辑器 (https://codeproject.org.cn/)
  • Mesh” - 使用 OpenGL 和 MFC 的小型 VRML 查看器 (https://codeproject.org.cn/)
  • SimpleCtrl” - CLayoutViewCView 的 MFC 简单示例

托管在 WinForms

  • FormsDemo_UmlEditor.exe - 托管 MFC 应用程序“UmlEditor”和“DialogEditor”的容器
  • MdiFormsDemo.exe - 托管“UmlEditor”的完整版本
  • SdiFormsDemo.exe - 托管“DialogEditor”的完整版本
  • FormDemo_SimpleCtrl.exe - CScrollViewCListCtrl 的简单测试
  • FormsDemo_DialogWiz.exe - 托管“DialogWiz

托管在 WPF

  • WpfDemo_UmlEditor.exe - 托管 MFC 应用程序“UmlEditor”和“DialogEditor”的容器
  • WpfPageDemo_UmlEditor.exe - WPF FlowDocument。托管 MFC 应用程序“UmlEditor”和“DialogEditor”的容器
  • WpfPageDemo_WrlViewer.exe - 托管 VRML 查看器

只需运行必要的应用程序即可。

源代码

注意MfcAdapter 3.0 演示仅支持一种配置:x86 Debug, Unicode, .NET 4.0-4.7

解压后,我们有一个 VS 2019 解决方案 - \Samples\Src\Samples.sln,其中包含所有演示项目。所有项目的构建输出都将连同必要的 MfcAdapter DLLs 一起复制到公共执行目录 \Samples\Src\x86\Debug 中。

如果出现引用错误,只需重复构建即可。

我试图使封装的 MFC 代码尽可能简洁,因此我没有修复此代码的一些原生问题,例如,编译器警告、打开不存在的最近文件时未处理的异常等等。

托管您的 MFC 应用程序

要托管您的 MFC 应用程序,请使用 MfcAdapter 的 x86 Debug Unicode .NET 4.0-4.7 版本

\Debug:

  • MfcAppAdapter.dll, ViewControl.dll, ViewFrameworkElement.dll

\Include:

  • HostApp.h, LayoutView.h

帮助:

  • MfcAdapterAPI.chm, ProgrammingGuide.htm

关注点

我认为本文的解决方案——带有 CWinApp 派生类的 MFC 扩展 DLL——允许您通过更改 MDI 类并使用合适的包装器,将 MFC 应用程序托管在任何(不限于托管 WinForms/WPF)Win32 无 MFC 框架中。否则,这可能是一个有用的示例,用于在 WinForms 组件 (UmlEditorComponent) 中进行 Visual Studio 设计器编程,因为它支持在设计器生成的代码中设置 Host。

历史

  • 2005年12月30日:首次发布
  • 2022年5月31日:文章更新
© . All rights reserved.