将 WPF 嵌入到 MFC 中,无法响应键盘输入
在 将 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,对这个问题是没有帮助的,因为只要设置成了父子窗口,消息循环就会合并。
继续调查,在搜索中,看到类似 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
How to eat keys in WM_KEYDOWN - Stack Overflow