有一个现存的 MFC 项目,需要在里面添加新的 UI 界面,使用 MFC 开发太费劲,完全使用 WPF 再重写一遍,时间上不允许。
可以考虑直接将 WPF 窗口嵌入到 MFC 窗口中,以下是探索过程中的一些记录。
参照如下博客的说明,使用 MFC + .NET Framework WPF 的方式,实现嵌入功能。
MFC中调用WPF教程_system::windows::interop;-CSDN博客
MFC中调用WPF教程 | Microsoft Learn(https://learn.microsoft.com/zh-cn/previous-versions/msdn10/ff849178(v=msdn.10%29)
为当前 MFC 项目添加公共语言运行时支持
这里只能使用 .NET Framework 的框架支持,而不能使用 .NET Core,因为使用 .NET Core 要求当前项目的输出类型为“类库”,而不能是可执行文件。编译时会报相关的错误。
为当前 MFC 项目添加 .NET Framework 框架的必要引用
新建 WPF 项目,并删除 WPF 工程中的 App.xaml 和 App.xaml.cs 两个源文件,修改项目 Output type (输出类型)为 Class Library (类库)。
并在 MFC 项目中,添加对 WPF 项目的引用。
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();}
先获取 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 中的窗口。
1 需要将现有 MFC 项目修改成 C++/CLI 项目(添加托管运行时支持)
这个需要根据实际情况,或许修改之后会有其它影响
2 在部分电脑上,WPF 嵌入 MFC 窗口中之后,会出现窗口中的控件渲染闪烁的问题
我这里两台电脑测试,有一台有问题,另一台 OK。不确定是哪里的问题。
以上使用 .NET Framework 的方案,是参考上面搜索出来的博客来实现的。现在更推荐使用 .NET Core 版本。
具体实现时,不能直接为 MFC 项目添加 .NET Core 的运行时支持,因为添加 .NET Core 支持要求 C++ 项目是类库,而不是可执行文件。
这就需要一个 C++/CLI 的中间层项目,来进行中转,与 《C++ 调用 C# - C++/CLI 方案》 中提到的是一样的。
新建基于 .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; } }}
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;}
与 《C++ 调用 C# - C++/CLI 方案》 中提到的一样,需要如下步骤:
HostWPFNative.h
所在的目录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
原文链接: https://cloud.tencent.com/developer/article/2481583
本作品采用 「署名 4.0 国际」 许可协议进行许可,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。