Skip to content

将 WPF 嵌入到 MFC 中,无法响应键盘输入

Published: at 19:41

将 WPF 窗口嵌入到 MFC 窗口中 中提到,可以将 WPF 嵌入到 MFC 窗口中, 但遗留了一个没有发现的问题,WPF 界面,无法响应键盘的输入。

示例源码已经在 https://gitee.com/Jasongrass/DemoPark/tree/master/Code/Embed_WPF_to_MFC/MFCMerge

🍕 问题调查

遇到键盘无法响应,怀疑就是消息循环的处理问题,但不确定具体的细节。

首先尝试将 WPF 的窗口运行,放在一个独立的线程中,类似这样:

private static void StartNewWindow()
{
Thread staThread = new Thread(() =>
{
// 创建一个新的窗口
_mainWindow = new MainWindow() { Top = -100000 };
_mainWindow.Show();
var interopHelper = new WindowInteropHelper(_mainWindow);
_mainWindowPtr = (int)(interopHelper.Handle);
MainWindow.myHwnd = _mainWindowPtr;
// 开始消息循环
System.Windows.Threading.Dispatcher.Run();
});
// 设置线程为 STA
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start();
}

WPF 界面在 MFC 中首次加载之后,确实可以在 TextBox 输入,但只要 MFC 获取焦点,再重新回到 WPF 界面,就无法输入了。

其实,在另一个线程或者进程启动 WPF,对这个问题是没有帮助的,因为只要设置成了父子窗口,消息循环就会合并。

使用 SetParent 跨进程设置父子窗口时的一些问题(小心卡死) - walterlv

继续调查,在搜索中,看到类似 ElementHost HwndSource 这样的关键词,以为是需要用这些将 WPF 窗口包装一下,再嵌入到 MFC 中,但实际上也是无效的。

ElementHost

Is it possible to host WPF Core (.NET 5.0) content in an MFC application? - Microsoft Q&A

HwndSource

Host WPF content in Win32 | Microsoft Learn

HwndSource Class (System.Windows.Interop) | Microsoft Learn

How do I host WPF content in MFC Applications? - Stack Overflow

问题的关键不在这里

🍕 问题解决

问题的关键是 WM_GETDLGCODE 这个消息

winapi - Non-Modal WPF control hosted in MFC Dialog does not receive keyboard input - Stack Overflow

WM_GETDLGCODE message (Winuser.h) - Win32 apps | Microsoft Learn

By default, the system handles all keyboard input to the control; the system interprets certain types of keyboard input as dialog box navigation keys. To override this default behavior, the control can respond to the WM_GETDLGCODE message to indicate the types of input it wants to process itself. 默认情况下,系统处理控件的所有键盘输入;系统将某些类型的键盘输入解释为对话框导航键。要覆盖此默认行为,控件可以响应 WM_GETDLGCODE 消息以指示它想要自行处理的输入类型。

默认情况下,在 dialog 中,键盘输入是被拦截的,所以只需要处理 WM_GETDLGCODE 就可以了。

using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace MyWPFApp
{
public partial class MainWindow : Window
{
// 定义窗口过程的委托
private HwndSourceHook? _hwndSourceHook;
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
HwndSource? source = PresentationSource.FromVisual(this) as HwndSource;
if (source != null)
{
// 在窗口初始化时设置窗口过程钩子
_hwndSourceHook = new HwndSourceHook(WndProc);
source.AddHook(_hwndSourceHook);
}
}
// 窗口过程函数
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
const int WM_GETDLGCODE = 0x0087;
const int DLGC_WANTALLKEYS = 4;
// 处理 WM_GETDLGCODE 消息
if (msg == WM_GETDLGCODE)
{
handled = true;
return new IntPtr(DLGC_WANTALLKEYS);
}
return IntPtr.Zero; // 继续传递其他消息
}
protected override void OnClosed(EventArgs e)
{
// 清理钩子
if (_hwndSourceHook != null)
{
HwndSource? source = PresentationSource.FromVisual(this) as HwndSource;
if (source != null)
{
source.RemoveHook(_hwndSourceHook);
}
}
base.OnClosed(e);
}
}
}

🍕 More about WM_GETDLGCODE

搜索的时候发现 Raymond Chen 大佬的两篇文章,可以更好的理解 WM_GETDLGCODE

那些不理解对话管理器的人注定要重新实现它,而且很糟糕 - Those who do not understand the dialog manager are doomed to reimplement it, badly - The Old New Thing

WM_GETDLGCODE 消息是一条查询消息,不应修改状态 - The WM_GETDLGCODE message is a query message and should not modify state - The Old New Thing

How to eat keys in WM_KEYDOWN - Stack Overflow

🍕 OTHER

WPF 让窗口激活作为前台最上层窗口的方法 | 林德熙


原文链接: https://blog.jgrass.cc/posts/host-wpf-in-mfc-cannot-input/

本作品采用 「署名 4.0 国际」 许可协议进行许可,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。