Loading

将 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,对这个问题是没有帮助的,因为只要设置成了父子窗口,消息循环就会合并。

使用 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://www.cnblogs.com/jasongrass/p/18555472

posted @ 2024-11-19 19:49  J.晒太阳的猫  阅读(5)  评论(0编辑  收藏  举报