WPF触摸应用程序(部分)在.NET Framework 4.7上冻结

更新

微软承认这个问题 :

Gepost door Microsoft op 13/10/2017 om 11:38

感谢您报告此事。 我们已经意识到这个问题,并且正在将其修复到.NET的未来版本中。 还有一个相关问题正在服务修复中发布,这将大大降低遇到此问题的可能性。 这将很快得到服务。

问题

我们的WPF应用程序正在使用触摸(没有手写笔)的平板电脑上使用,我们在安装.NET Framework 4.7后遇到问题。 使用应用程序一段时间后可能会出现两种情况:应用程序完全冻结并且必须重新启动,或者禁用PopupWindow元素中的所有触摸function。 两者之间有很大的不同,但我相信原因是一样的。

场景1:完全冻结

  • 应用程序完全没有响应,必须使用任务管理器关闭应用程序
  • 可以使用触摸或鼠标
  • 有时在应用程序挂起之前会抛出以下错误:

指数数组的边界之外。

这是堆栈跟踪:

  at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add) at System.Windows.Input.StylusWisp.WispLogic.CoalesceAndQueueStylusEvent(RawStylusInputReport inputReport) at System.Windows.Input.StylusWisp.WispLogic.ProcessSystemEvent(PenContext penContext, Int32 tabletDeviceId, Int32 stylusDeviceId, Int32 timestamp, SystemGesture systemGesture, Int32 gestureX, Int32 gestureY, Int32 buttonState, PresentationSource inputSource) at System.Windows.Input.PenContext.FireSystemGesture(Int32 stylusPointerId, Int32 timestamp) at System.Windows.Input.PenThreadWorker.FireEvent(PenContext penContext, Int32 evt, Int32 stylusPointerId, Int32 cPackets, Int32 cbPacket, IntPtr pPackets) at System.Windows.Input.PenThreadWorker.ThreadProc() at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() 

场景2:部分冻结

  • 主窗口仍然响应(通过鼠标和触摸)但任何“叠加”内容(模式对话框, Window ,来自DatePicker Popup元素, ComboBox ,…)都不响应点击。 必须重新启动应用程序才能重新启用触摸。
  • 鼠标仍可用于“叠加”元素。

此处还详细说明了此问题。 可以在此处找到问题发生后的行为video。

附加信息

  • 这两种方案都可以在不同类型的平板电脑上进行模拟,也可以在Windows模拟器上使用Windows 8.1和Windows 10进行模拟。
  • 删除.NET Framework 4.7时,问题已得到解决
  • 通过使用多个手指快速点击一些ComboBox元素,可以轻松地再现场景2。 几分钟后,弹出窗口不再响应触摸。
  • 场景1更难以模拟并随机发生。

原因

这个问题似乎与StylusWisp代码有关。 我猜它突然失败并在那之后变得无法使用。

通过使用DisableWPFTabletSupport或DisableStylusAndTouchSupport禁用Stylus支持时,问题将消失。 但是,任何具有PanningMode="Both" ScrollViewer PanningMode="Both"滑动滚动。

解?

微软也报告过类似的问题 。 由于目前还没有太多的支持,修复可能需要一段时间。 与此同时,我正在寻找一个解决此问题的解决方案,该解决方案不涉及禁用.NET Framework 4.7并保持原始触摸支持不变 。 有没有人有同样的问题和更好的解决方案?

更新:解决方法解决方案的解决方法不起作用。
问题是所有手指敲击都被解释为鼠标点击。 自定义触摸滚动仅适用于对鼠标点击没有反应的内容。 为了使其正常工作,您需要找到一种在执行滚动操作时“吃掉”鼠标单击事件的方法。

我可能已经找到了破损的触摸滚动的解决方法。
处理WM_TOUCH并使用自定义TouchDevice。
感谢Luca Cornazzani: 在WPF控件上启用多点触控
我使用的另一个来源(用于TOUCHINPUT定义): WPF和多点触控

在应用程序启动时调用着名的DisableWPFTabletSupport函数。

MainWindow.xaml:

             

MainWindow.xaml.vb:

 Class MainWindow Private _devices As New Dictionary(Of Integer, TouchDeviceEmulator)() Public Sub New() ' This call is required by the designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. For i As Integer = 1 To 19 Dim myComboBoxItem As ComboBoxItem = New ComboBoxItem myComboBoxItem.Content = "ComboBoxItem " & i.ToString() comboBox1.Items.Add(myComboBoxItem) Next For i As Integer = 65 To 90 Dim c As Char = ChrW(i) For j As Integer = 1 To 10 textBlock1.Text += " ".PadLeft(10, c) Next textBlock1.Text += vbCrLf Next End Sub Protected Overrides Sub OnSourceInitialized(e As EventArgs) MyBase.OnSourceInitialized(e) Dim source As Interop.HwndSource = TryCast(PresentationSource.FromVisual(Me), Interop.HwndSource) source.AddHook(New Interop.HwndSourceHook(AddressOf WndProc)) Dim presentation = DirectCast(PresentationSource.FromDependencyObject(Me), Interop.HwndSource) If presentation Is Nothing Then Throw New Exception("Unable to find the parent element host.") End If RegisterTouchWindow(presentation.Handle, TouchWindowFlag.WantPalm) End Sub Private Function WndProc(hwnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr, ByRef handled As Boolean) As IntPtr ' Handle messages... If msg = WM_TOUCH Then handled = HandleTouch(wParam, lParam) Return New IntPtr(1) End If Return IntPtr.Zero End Function Private Function HandleTouch(wParam As IntPtr, lParam As IntPtr) As Boolean Dim handled As Boolean = False Dim inputCount = wParam.ToInt32() And &HFFFF Dim inputs = New TOUCHINPUT(inputCount - 1) {} If GetTouchInputInfo(lParam, inputCount, inputs, Runtime.InteropServices.Marshal.SizeOf(inputs(0))) Then For i As Integer = 0 To inputCount - 1 Dim input As TOUCHINPUT = inputs(i) 'TOUCHINFO point coordinates and contact size is in 1/100 of a pixel; convert it to pixels. 'Also convert screen to client coordinates. Dim position As Point = PointFromScreen(New System.Windows.Point((input.x * 0.01), (input.y * 0.01))) Dim device As TouchDeviceEmulator = Nothing If Not _devices.TryGetValue(input.dwID, device) Then device = New TouchDeviceEmulator(input.dwID) _devices.Add(input.dwID, device) End If device.Position = position If (input.dwFlags And TOUCHEVENTF_DOWN) > 0 Then device.SetActiveSource(PresentationSource.FromVisual(Me)) device.Activate() device.ReportDown() ElseIf device.IsActive AndAlso (input.dwFlags And TOUCHEVENTF_UP) > 0 Then device.ReportUp() device.Deactivate() _devices.Remove(input.dwID) ElseIf device.IsActive AndAlso (input.dwFlags And TOUCHEVENTF_MOVE) > 0 Then device.ReportMove() End If Next CloseTouchInputHandle(lParam) handled = True End If Return handled End Function Private Class TouchDeviceEmulator Inherits TouchDevice Public Position As System.Windows.Point Public Sub New(deviceId As Integer) MyBase.New(deviceId) End Sub Public Overrides Function GetTouchPoint(relativeTo As IInputElement) As TouchPoint Dim pt As System.Windows.Point = Position If relativeTo IsNot Nothing Then pt = ActiveSource.RootVisual.TransformToDescendant(DirectCast(relativeTo, Visual)).Transform(Position) End If Dim rect = New Rect(pt, New Size(1.0, 1.0)) Return New TouchPoint(Me, pt, rect, TouchAction.Move) End Function Public Overrides Function GetIntermediateTouchPoints(relativeTo As IInputElement) As TouchPointCollection Throw New NotImplementedException() End Function Public Overloads Sub SetActiveSource(activeSource As PresentationSource) MyBase.SetActiveSource(activeSource) End Sub Public Overloads Sub Activate() MyBase.Activate() End Sub Public Overloads Sub ReportUp() MyBase.ReportUp() End Sub Public Overloads Sub ReportDown() MyBase.ReportDown() End Sub Public Overloads Sub ReportMove() MyBase.ReportMove() End Sub Public Overloads Sub Deactivate() MyBase.Deactivate() End Sub End Class Private Const WM_TOUCH As Integer = &H240 Private Enum TouchWindowFlag As UInteger FineTouch = &H1 WantPalm = &H2 End Enum ' Touch event flags ((TOUCHINPUT.dwFlags) [winuser.h] Private Const TOUCHEVENTF_MOVE As Integer = &H1 Private Const TOUCHEVENTF_DOWN As Integer = &H2 Private Const TOUCHEVENTF_UP As Integer = &H4 Private Const TOUCHEVENTF_INRANGE As Integer = &H8 Private Const TOUCHEVENTF_PRIMARY As Integer = &H10 Private Const TOUCHEVENTF_NOCOALESCE As Integer = &H20 Private Const TOUCHEVENTF_PEN As Integer = &H40 Private Const TOUCHEVENTF_PALM As Integer = &H80  Private Structure TOUCHINPUT Public x As Int32 Public y As Int32 Public hSource As IntPtr Public dwID As Int32 Public dwFlags As Int32 Public dwMask As Int32 Public dwTime As Int32 Public dwExtraInfo As IntPtr Public cxContact As Int32 Public cyContact As Int32 End Structure  Private Shared Function RegisterTouchWindow(hWnd As System.IntPtr, flags As TouchWindowFlag) As Boolean End Function  Private Shared Function GetTouchInputInfo(hTouchInput As IntPtr, cInputs As Int32,  pInputs As TOUCHINPUT(), cbSize As Int32) As  [Boolean] End Function  Private Shared Sub CloseTouchInputHandle(lParam As System.IntPtr) End Sub End Class 

此示例中的大多数代码与Cornazzani的C#代码相同。
至少对于ScrollViewer它似乎工作,没有测试过其他控件。
它不能解决部分损坏的Stylus支持的问题。 在InkCanvas上编写的工作并不像以前那么顺利,并且使用DisableWPFTabletSupport hack橡皮擦按钮根本不起作用。

同样有趣,相同的方法:github上的WmTouchDevice。

安装.NET Framework 4.7.1似乎解决了这个问题。 .NET Framework 4.7.1也包含在自10月开始推出的Windows 10 Fall Creators Update中。