Приложение WPF touch (частично) зависает on.NET рамки 4.7

обновление

Microsoft признала проблему:

Gepost дверь Microsoft op 13/10/2017 om 11: 38

Спасибо, что сообщили об этом. Мы знаем об этой проблеме и исправляем ее в будущей версии .Сеть. Существует также связанная с этим проблема, которая выпускается в исправлении обслуживания, которое резко уменьшит возможность попадания в эту проблему. Это будет обслуживаться относительно скоро.

наше приложение WPF используется на планшетах с помощью touch (без стилуса), и мы испытываем проблемы после установки .NET Framework 4.7. После использования приложения на некоторое время могут возникнуть два сценария: либо приложение полностью зависает и должно быть перезапущено, либо все сенсорные функции в Popup или Window элементы отключена. Между ними есть большая разница, но я верю в причину. быть одинаковым.

Сценарий 1: полная заморозка

  • приложение становится полностью не отвечает, приложение должно быть закрыто с помощью Диспетчера задач
  • Touch ни мышь не может быть использован
  • иногда перед зависанием приложения возникает следующая ошибка:

индекс находился за пределами массива.

это stacktrace:

   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, Popup элемент DatePicker, ComboBox, ...) не реагирует на нажатие. Приложение должно быть перезапущено для reenable touch.
  • мышь все еще может использоваться в элементах "overlay".

этот вопрос также подробно объясняется здесь. Ля видео поведения после возникновения проблемы можно найти здесь.

Дополнительная Информация

  • оба сценария могут быть смоделированы на разных типах планшетов, а также на симуляторе Windows, используя сочетание Windows 8.1 и Windows 10.
  • проблемы исправлены при удалении .NET Framework 4.7
  • Сценарий 2 можно легко воспроизвести, быстро нажав некоторые ComboBox элементы с несколькими пальцами. Через несколько минут всплывающее окно больше не реагирует на прикосновение.
  • Сценарий 1 сложнее моделировать и происходит случайным образом.

причина

проблема, похоже, имеет какое-то отношение к коду StylusWisp. Я думаю, что он внезапно терпит неудачу и становится непригодным после этого момента.

при отключении поддержки стилуса с помощью DisableWPFTabletSupport или DisableStylusAndTouchSupport проблема исчезает. Впрочем, никаких ScrollViewer С PanningMode="Both" не может быть пальцем не прокручивается.

решение?

A аналогичная проблема было сообщено в Microsoft. Поскольку пока нет большой поддержки, исправление может занять некоторое время. Тем временем я ищу решение для этой проблемы, которая не включает отключение .NET Framework 4.7 и это держит первоначально поддержка касания intact. У кого-нибудь есть те же проблемы и лучшее решение?

2 ответов


обновление: Ниже обходной путь для решения обходного пути не работает хорошо.
Проблема в том, что все прикосновения воспринимаются как щелчки мыши. Пользовательский сенсорный прокрутки работает только гладко для контента, который не реагирует на щелчки мыши. Чтобы он работал нормально, вам нужно будет найти способ "съесть" события щелчка мыши при выполнении действия прокрутки.

возможно, я нашел обходной путь для сломанной прокрутки touch.
Ручка WM_TOUCH и использовать пользовательские TouchDevice.
Заслуга Луки Cornazzani: включить мультитач на элементах управления WPF
Другой источник, который я использовал (для определения TOUCHINPUT):WPF и мультитач

при запуске приложения вызовите известную функцию 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.В. Б.:

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

большинство кодов в этом примере идентичны коду C# Cornazzani.
По крайней мере, для ScrollViewer, похоже, работает, не тестировал другие элементы управления.
Это не решает мою проблему с частично сломанной поддержкой стилуса. Запись на InkCanvas работает не так гладко, как раньше, и кнопка ластика не работает вообще с disablewpftabletsupport hack.

также интересный, тот же подход: WmTouchDevice на github.


установка .NET Framework 4.7.1 Кажется, исправить проблему. .NET Framework 4.7.1 также включен в обновление Windows 10 Fall Creators, которое началось с октября.