WPF模式下Chrome触摸失效的解决方案

现象:


从wpf画面跳转到Chrome画面会产生触摸失灵的现象。过一段时间后自动恢复。

原因分析

查阅相关资料后对产生现象的原因进行分析
1.触摸屏使用USB连接的外部连接设备。只能被一个进程独立占用。
2.触摸的消息传递机制是由微软系统本身控制的。
3.暴力测试后发现某些其他设备也会存在相关的无法触摸问题。

总结:当触摸点落在wpf程序上时,触发退出机制,触摸点未及时挪开,导致触摸点又落在浏览器上。系统未及时转交触摸权限给浏览器。导致触摸事件无法传达给浏览器。造成触摸失灵的假死现象。(鼠标点击正常无误)

解决方案

方法1.由于是触摸点未计时释放造成的,所以我们退出机制改为WPF程序不存在触摸点后退出程序。
在 windows 的头部加入下列参数

TouchEnter="Window_enter"  
TouchLeave="Window_TouchLeaver"  
Stylus.IsPressAndHoldEnabled="false" 
Stylus.IsFlicksEnabled="false"

TouchEnter 代码如下:

lock (locker)
 	{
    touchid.Add(e.TouchDevice.Id);
	}
  log.WriteLogFile(e.TouchDevice.Id.ToString(), "TouchDeviceID");

记录下按下的触摸点ID。

TouchLeave 代码如下:

lock (locker)
                    {
                        touchid.Remove(e.TouchDevice.Id);
                    }
                    if (touchid?.Count() > 0)
                    {
                        return;
                    }
                    this.Close();

监测触摸点是否完全释放,只有在完全释放时会执行关闭。

效果:


方法2.之前分析,是由于系统未及时将触摸的权限分配给浏览器,所以造成的假死,那我们可以用代码在关闭wpf程序后人为的手动激活一次浏览器。

代码如下

这是标准的微软提供的激活代码

private bool SetTopWindow(IntPtr hWnd)
        {
            try
            {
                IntPtr hForeWnd = GetForegroundWindow();
                int dwForeID;
                GetWindowThreadProcessId(hForeWnd, out dwForeID);
                IntPtr dwCurID = GetCurrentThreadId();
                AttachThreadInput(dwCurID.ToInt32(), dwForeID, 1);
                SwitchToThisWindow(hWnd, true);
                ShowWindow(hWnd, SW_SHOWMAXIMIZED);
                SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
                SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
                SetForegroundWindow(hWnd);
                AttachThreadInput(dwCurID.ToInt32(), dwForeID, 0);
                SetFocus(hWnd);
                return true;
            }
            catch (Exception ex)
            {
                log.WriteLogFile(ex.ToString(), "TimeMonitoring");
                return false;
            }
        }

调用如下

Process[] pro = Process.GetProcesses();
                                for (int i = 0; i < pro.Length; i++)
                                {
                                    if (pro[i].ProcessName.ToLower().Trim().Contains("chrome"))
                                    {
                                        try
                                        {
                                            var x = SetTopWindow(pro[i].MainWindowHandle);
                                        }
                                        catch (Exception ex)
                                        {
                                            log.WriteLogFile(ex.ToString(), "TimeMonitoring");
                                        }
                                    }
                                }

相关Api参照标准定义方式:

[DllImport("user32.dll")]
 public static extern int AttachThreadInput(int idAttach, int idAttchTo, int fAttch);

方法3.查阅相关资料后,发现这类问题是.NETFramework 4.5出现的BUG,一直到4.7才修复,所以我们将框架升级到4.7之上。同时在程序启动最开始的地方加入

<runtime>
    <AppContextSwitchOverrides value="Switch.System.Windows.Input.Stylus.EnablePointerSupport=true" />
  </runtime>
完整版

<?xml version="1.0" encoding="utf-8"?>

<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7" />
  </startup>
  <runtime>
    <AppContextSwitchOverrides value="Switch.System.Windows.Input.Stylus.EnablePointerSupport=true" />
  </runtime>
</configuration>

总结

三种方法都实际经过上线测试。如果有更好的做法希望可以相互交流,共同学习