【问题标题】:WPF touch application (partially) freezes on .NET Framework 4.7WPF 触摸应用程序(部分)在 .NET Framework 4.7 上冻结
【发布时间】:2017-09-05 07:51:47
【问题描述】:

更新

Microsoft acknowledged the issue:

Gepost 门微软 op 13/10/2017 om 11:38

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

问题

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

场景 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:部分冻结

  • 主窗口仍然响应(通过鼠标和触摸),但任何“覆盖”内容(模态对话框,WindowPopup 元素来自DatePickerComboBox,...)没有响应去敲击。必须重新启动应用才能重新启用触控。
  • 鼠标仍可用于“叠加”元素。

这个问题here也有详细解释。 问题发生后的行为视频可以在here找到。

其他信息

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

原因

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

使用DisableWPFTabletSupportDisableStylusAndTouchSupport 禁用手写笔支持时,问题消失。但是,任何带有PanningMode="Both"ScrollViewer 都不能再滑动滚动。

解决方案?

已向 Microsoft 报告了 similar issue。由于还没有太多支持,修复可能需要一段时间。与此同时,我正在为这个问题寻找一个不涉及禁用 .NET Framework 4.7 并且保持原始触摸支持完整的解决方案。有没有人有同样的问题和更好的解决方案?

【问题讨论】:

  • [在 WispLogic.CoalesceAndQueueStylusEvent 中引发 IndexOutOfRangeException · 问题 #935 · dotnet/wpf](github.com/dotnet/wpf/issues/935)
  • 它将在 .NET Core 3.0.100-preview5-011568 和 .NET Framework 4.7 Microsoft Windows NT 10.0.15063.0 中中断

标签: c# wpf touch .net-4.7


【解决方案1】:

为什么它会在 .NET Framework 4.7 for Windows10 Creators Update 中中断?

我的朋友lsjhttps://referencesource.microsoft.com下载源代码,发现.NET 4.7 for Windows 10 Creators Update没有给WispLogic.CoalesceAndQueueStylusEvent加锁,另一个加锁。

所以它只在 .NET Framework 4.7 for Windows10 Creators Update 中中断。并且.NET Framework 4.7 RTM 将锁添加到WispLogic.CoalesceAndQueueStylusEvent 并修复它。

哪个版本加锁?

哪个版本解除锁定?

有趣的是.NET 4.7 RTM 将锁添加到WispLogic.CoalesceAndQueueStylusEvent.NET 4.7.1 Downlevel 删除它。

.NET 4.7.2 for Windows 10 April 2018 Creators Update 是第一个添加锁的版本,它似乎在 .NET 4.7.2 中修复而不在 .NET 4.7.1 中修复

【讨论】:

  • 实际上,这个答案可能会被标记为比所选答案更好,因为微软有时会修复这个问题然后打开另一个,有时会修复那个问题然后打开这个。
【解决方案2】:

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

【讨论】:

  • 它将在 .NET Core 3.0.100-preview5-011568 中中断
【解决方案3】:

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

我可能找到了解决触摸滚动损坏的方法。
处理 WM_TOUCH 并使用自定义 TouchDevice。
归功于 Luca Cornazzani:Enable multitouch on WPF controls
我使用的另一个来源(用于 TOUCHINPUT 定义):WPF and multi-touch

在应用启动时调用众所周知的 DisableWPFTabletSupport 函数。

MainWindow.xaml:

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TouchScrollTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="395.603" Width="525">
    <Grid>
        <StackPanel>
            <ComboBox x:Name="comboBox1" FontSize="16" Width="150">
            </ComboBox>

            <ScrollViewer Height="300" Width="300" PanningMode="Both" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
                <TextBlock x:Name="textBlock1">
                </TextBlock>
            </ScrollViewer>
        </StackPanel>        
    </Grid>
</Window>

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

    <Runtime.InteropServices.StructLayout(Runtime.InteropServices.LayoutKind.Sequential)>
    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

    <Runtime.InteropServices.DllImport("user32")>
    Private Shared Function RegisterTouchWindow(hWnd As System.IntPtr, flags As TouchWindowFlag) As Boolean
    End Function

    <Runtime.InteropServices.DllImport("user32")>
    Private Shared Function GetTouchInputInfo(hTouchInput As IntPtr, cInputs As Int32, <Runtime.InteropServices.[In], Runtime.InteropServices.Out> pInputs As TOUCHINPUT(), cbSize As Int32) As <Runtime.InteropServices.MarshalAs(Runtime.InteropServices.UnmanagedType.Bool)> [Boolean]
    End Function

    <Runtime.InteropServices.DllImport("user32")>
    Private Shared Sub CloseTouchInputHandle(lParam As System.IntPtr)
    End Sub

End Class

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-07
    • 2020-10-02
    • 1970-01-01
    • 2023-03-08
    相关资源
    最近更新 更多