将 WPF 窗口嵌入到 MFC 窗口中

背景#

有一个现存的 MFC 项目,需要在里面添加新的 UI 界面,使用 MFC 开发太费劲,完全使用 WPF 再重写一遍,时间上不允许。

可以考虑直接将 WPF 窗口嵌入到 MFC 窗口中,以下是探索过程中的一些记录。

🍕 方案1 MFC + .NET Framework WPF#

参照如下博客的说明,使用 MFC + .NET Framework WPF 的方式,实现嵌入功能。

MFC中调用WPF教程_system::windows::interop;-CSDN博客
MFC中调用WPF教程 | Microsoft Learn

Step1 当前 MFC 项目修改#

为当前 MFC 项目添加公共语言运行时支持

这里只能使用 .NET Framework 的框架支持,而不能使用 .NET Core,因为使用 .NET Core 要求当前项目的输出类型为“类库”,而不能是可执行文件。编译时会报相关的错误。

为当前 MFC 项目添加 .NET Framework 框架的必要引用

Step2 新建 .NET Framework WPF 项目#

新建 WPF 项目,并删除 WPF 工程中的 App.xaml 和 App.xaml.cs 两个源文件,修改项目 Output type (输出类型)为 Class Library (类库)。

并在 MFC 项目中,添加对 WPF 项目的引用。

Step3 添加托管 C++ 类#

// CHostWPFWnd.h
#pragma once

using namespace System;
using namespace System::Windows;
using namespace System::Windows::Interop;
using namespace System::Runtime;
using namespace MyWpfApp;

public ref class CHostWPFWnd
{
public:
  static HWND GetWPFWindowHwnd();
  static void ShowWPFWindow(HWND hwnd);
};
// CHostWPFWnd.cpp
#include "pch.h"
#include "CHostWPFWnd.h"

// 获取 WPF 窗口的句柄
HWND CHostWPFWnd::GetWPFWindowHwnd()
{
  MainWindow^ wpfWindow = gcnew MainWindow();

  // 需要先将窗口显示出来(显示到屏幕外,避免在屏幕中间闪现一下)
  // 才能拿到有效的窗口句柄
  wpfWindow->Top = -100000; 
  wpfWindow->Show();  

  WindowInteropHelper^ wih = gcnew WindowInteropHelper(wpfWindow); 
  HWND h = (HWND)(wih->Handle.ToPointer());
  return h;
}

// 以独立的弹窗直接显示 WPF 窗口
void CHostWPFWnd::ShowWPFWindow(HWND hwnd)
{
  MainWindow^ wpfWindow = gcnew MainWindow();
  WindowInteropHelper^ wih = gcnew WindowInteropHelper(wpfWindow);
  wih->Owner = IntPtr(hwnd);
  wpfWindow->Show();
}

Step4 在 MFC 代码中调用#

先获取 MFC 窗口的句柄,然后调用 ShowWPFWindow 方法,显示独立的 WPF 弹窗

#include "CHostWPFWnd.h"
#include <Windows.h>

HWND cppWindowHwnd = this->GetSafeHwnd();
CHostWPFWnd::ShowWPFWindow(cppWindowHwnd);

获取到 WPF 窗口的句柄,然后将其嵌入到 MFC 窗口中。

为了避免初始化 WPF 窗口时,在系统任务栏上闪现 WPF 窗口的标题,可以在 WPF 中设置 ShowInTaskbar="False"

#include "CHostWPFWnd.h"
#include <Windows.h>

HWND cppWindowHwnd = this->GetSafeHwnd();
HWND wpfWindowHwnd = CHostWPFWnd::GetWPFWindowHwnd();

::SetParent(wpfWindowHwnd, cppWindowHwnd);
::SetWindowLongPtr(wpfWindowHwnd, GWL_STYLE, WS_CHILD | WS_VISIBLE);
::MoveWindow(wpfWindowHwnd, 300, 20, 400, 300, TRUE);

::BringWindowToTop(wpfWindowHwnd);

主体窗口是 MFC 的窗口和控件,有青色背景的是 WPF 嵌入到 MFC 中的窗口。

MFC + .NET Framework WPF 方案的问题#

1 需要将现有 MFC 项目修改成 C++/CLI 项目(添加托管运行时支持)

这个需要根据实际情况,或许修改之后会有其它影响

2 在部分电脑上,WPF 嵌入 MFC 窗口中之后,会出现窗口中的控件渲染闪烁的问题

我这里两台电脑测试,有一台有问题,另一台 OK。不确定是哪里的问题。

🍕 方案2 MFC + .NET Core WPF#

以上使用 .NET Framework 的方案,是参考上面搜索出来的博客来实现的。现在更推荐使用 .NET Core 版本。

具体实现时,不能直接为 MFC 项目添加 .NET Core 的运行时支持,因为添加 .NET Core 支持要求 C++ 项目是类库,而不是可执行文件。

这就需要一个 C++/CLI 的中间层项目,来进行中转,与 《C++ 调用 C# - C++/CLI 方案》 中提到的是一样的。

Step1 新建 .NET Core WPF 项目#

新建基于 .NET Core(如 .NET8)的 WPF 项目,并删除 App.xaml 和 App.xaml.cs 两个源文件,修改项目类型为类库。

<OutputType>Library</OutputType>

通过上面的代码,其实可以看到,将 WPF 窗口嵌入到 MFC 中,重点就是拿到 WPF 窗口的句柄,然后使用 Windows API 就可以将窗口嵌入了。这部分代码,其实可以在 C#/WPF 项目中直接实现,避免在 C++/CLI 中间层写太多代码(因为不习惯写 C++)。

namespace MyWPFApp
{
    public static class ExportWindowHelper
    {
        private static MainWindow? _mainWindow;
        private static int _mainWindowPtr;

        public static int GetMainWindow()
        {
            if (_mainWindow == null)
            {
                _mainWindow = new MainWindow { Top = -100000 };
                _mainWindow.Show();

                var interopHelper = new WindowInteropHelper(_mainWindow);

                _mainWindowPtr = (int)(interopHelper.Handle);
            }

            return _mainWindowPtr;
        }
    }
}

Step2 新建 C++/CLI 项目#

// HostWPFNative.h
#pragma once

#ifdef VIEW_BRIDGE_EXPORTS
#define VIEW_BRIDGE_API __declspec(dllexport)
#else
#define VIEW_BRIDGE_API __declspec(dllimport)
#endif

#include <string>

// 导出给原生 C++/MFC 项目使用的类
class VIEW_BRIDGE_API ViewBridgeWrapper
{
public:
    int GetHwnd();
};

// HostWPFWnd.h
#pragma once

using namespace System;
using namespace System::Windows;
using namespace System::Windows::Interop;
using namespace System::Runtime;
using namespace MyWPFApp;

// 托管 C++ 实现,调用 C# 获取窗口句柄
public ref class HostWPFWnd
{
public:
    int GetHwnd()
    {
        // 在 C# 中获取到窗口句柄
        int ptr2 = ExportWindowHelper::GetMainWindow();
        return ptr2;

        // 在 C++/CLI 中获取窗口句柄
        //MainWindow^ wpfWindow = gcnew MainWindow();
        //WindowInteropHelper^ wih = gcnew WindowInteropHelper(wpfWindow);
        //wpfWindow->Top = -100000;
        //wpfWindow->Show();
        //int ptr = (int)(wih->Handle.ToPointer());
        //return ptr;
    }
};
// HostWPFWnd.cpp
#include "pch.h"
#include "HostWPFWnd.h"
#include "HostWPFNative.h"

#define VIEW_BRIDGE_EXPORTS

// C++ 导出方法的实现,调用托管 C++ 方法,获取窗口句柄
int ViewBridgeWrapper::GetHwnd()
{
    HostWPFWnd wpfWnd;
    int ptr = wpfWnd.GetHwnd();
    return ptr;
}

Step3 MFC 中添加对中间层 C++/CLI 的引用#

《C++ 调用 C# - C++/CLI 方案》 中提到的一样,需要如下步骤:

  • 项目->属性->配置属性->VC++ 目录-> 在 "包含目录" 里添加头文件 HostWPFNative.h 所在的目录

  • 项目->属性->配置属性->VC++ 目录-> 在 "库目录" 里添加 ViewBridge.lib 所在的目录

  • 项目->属性->配置属性->链接器->输入-> 在 "附加依赖项" 里添加 ViewBridge.lib(若有多个 lib 则以空格隔开)


在 MFC 的业务代码中(窗口初始化代码等地方),调用上述方法,获取到 WPF 窗口的句柄,就可以嵌入到 MFC 窗口中了。

以下是代码参考

#include "HostWPFNative.h"

/// <summary>
/// SHOW WPF TEST
/// </summary>
void CMyMFCAppDlg::OnBnClickedButtonShowWpf()
{
  ViewBridgeWrapper viewWrapper;
  int ptr = viewWrapper.GetHwnd();  
  HWND cppWindowHwnd = this->GetSafeHwnd();
  HWND wpfWindowHwnd = (HWND)ptr; 

  // 保存 WPF 窗口句柄
  m_wpfMainWindowHwnd = wpfWindowHwnd;

  // 获取客户区的大小
  CRect clientRect;
  GetClientRect(&clientRect);
  int clientWidth = clientRect.Width();
  int clientHeight = clientRect.Height(); 

  ::SetParent(wpfWindowHwnd, cppWindowHwnd);
  ::SetWindowLongPtr(wpfWindowHwnd, GWL_STYLE, WS_CHILD | WS_VISIBLE);
  ::MoveWindow(wpfWindowHwnd, 10, 72, clientWidth-20, clientHeight-80, TRUE); 
  ::BringWindowToTop(wpfWindowHwnd);
}

void CMyMFCAppDlg::OnSize(UINT nType, int cx, int cy)
{
  CDialogEx::OnSize(nType, cx, cy);

  if (m_wpfMainWindowHwnd)
  {
    // 窗口大小变化时,调整 WPF 窗口的大小
    ::MoveWindow(m_wpfMainWindowHwnd, 10, 72, cx - 20, cy - 80, TRUE);
  }
}

🍕 源码参考#

https://gitee.com/Jasongrass/DemoPark/tree/master/Code/Embed_WPF_to_MFC

作者:JasonGrass

出处:https://www.cnblogs.com/jasongrass/p/18423860

版权:本作品采用「署名 4.0 国际」许可协议进行许可。

posted @   J.晒太阳的猫  阅读(151)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2023-09-21 使用 utools 调用欧路词典进行快捷查词
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示