1. 问题
在生产环境中,有一些场景需要窗体来响应键盘事件(注意,是窗体响应,而不是窗体上的控件响应),如解析扫码枪的扫描结果。但在嵌入WebView2的Form程序,Host Form无法对键盘事件(如窗体的KeyPress)进行截获,同样,也无法对WebView2本身进行键盘事件的响应处理。
2. 传统的键盘事件如何响应
在解决问题前,回顾一下在C#中,传统的窗体键盘事件如何响应。
- 在窗体属性中,设置KeyPreview属性为Ture;
- 在窗体事件中,为窗体添加KeyPress事件;
- 在IDE为你自动添加的响应处理函数中,编写你的处理逻辑
- private void Form1_KeyPress(object sender, KeyPressEventArgs e)
- {
- //编写你的响应逻辑
- MessageBox.Show(e.KeyChar.ToString());
- }
而在嵌入WebView2的Form程序,Form对键盘事件,无法按照传统处理方式进行截获,也就无法按照原来的处理方式处理了。
3. 问题产生的原因
先来梳理一下窗体响应事件的顺序。
- 当窗体上没有任何其他控件的时候,窗体是可以直接响应这些消息的,也就是说可以正常响应键盘事件。
- 但是当窗体存在其他控件后,我们会发现窗体再也不会响应按键消息了,因为这些消息都由其上的控件所处理掉并且不再发给父窗体。
- 通过Form类的KeyPreview的属性,它是可以接收得到按键消息。也就是第2部分提到的处理方式
分析到这里,基本上能找到webView2加入后引发的怪异行为了。总结起来就是:WebView2拦截了键盘事件。为什么WebView2要这么做?因为WebView本看上是浏览器内核,它要通过最高级别的键盘事件处理一些浏览器事件。如F5刷新,F12调试等,你会发现,哪怕焦点不在webView上,WebView2仍然能响应F5的刷新事件。当然,浏览器的快捷键可以被禁止,但这是另一话题。不可否认的事实就是:WebView影响了消息传递机制。微软可能也意识到这个问题,但对于浏览器的快捷键与用户自定义按键事件的协调,确实是个头疼的事情。这部分是我的猜测。
4. 问题的解决
托管(managed)方式的消息响应失效,尝试非托管方式的处理。思路是:调用Win32的钩子函数,对键盘事件进行拦截,同时将扫描行为进行包装,将获得到的扫描结果(以回边键为结束符),返回托管调用方。
- 编写单独的ScanerHook类,处理扫描键盘事件。
- 托管代码,通过注册ScanerEvent事件响应函数,来接收扫描结果。在WinForm中的关键代码如下:
- private ScanerHook listener = new ScanerHook();
- public WebViewForm()
- {
- InitializeComponent();
- InitializeAsync();
- listener.ScanerEvent += (s) =>
- {
- webView.CoreWebView2.PostWebMessageAsString(s.Result); //将扫描结果交给页面
- };
- }
- 当窗体打开时,在托管代码中安装钩子
- private void WebViewForm_Load(object sender, EventArgs e)
- {
- listener.Start();
- }
- 在窗体关闭时,不要忘记卸载钩子
- private void WebViewForm_FormClosed(object sender, FormClosedEventArgs e)
- {
- listener.Stop();
- }
至此,页面已经“具备了”扫描条码(二维码)的能力。示例代码是在VS2022,用.net6实现的。