将 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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2023-09-21 使用 utools 调用欧路词典进行快捷查词