Как отключить поддержку планшетов WPF в Surface 4 Pro?

Я унаследовал приложение WPF, предназначенное для Net 3.5, и мне нужно установить его на Surface Pro 4 (I5). Приложение зависает в разных точках, и я заметил, что анимации иногда никогда не запускают завершенное событие (возможно, они заканчиваются в какой-то момент, но не во время, указанное в свойстве Duration).

В качестве альтернативы я попытался отключить RealTimeStylus для приложений WPF, но после после нескольких испытаний я заметил, что, хотя метод DisableWPFTabletSupport выполняется и завершается (я добавил код журнала в метод DisableWPFTabletSupport, и четыре устройства удалены в Surface Pro 4), вероятно, поддержка планшетов WPF все еще активна в моем приложении, потому что приложение продолжает зависать время от времени и продолжает захватывать касания экрана.

Таким образом, единственный способ, которым я нашел возможность успешно запустить приложение WPF, ориентированное на Net 3.5, в Surface 4 Pro, — это использовать диспетчер устройств Windows для отключения всех устройств, связанных с сенсорным экраном, в интерфейсах человека.

Кто-нибудь знает, как отключить поддержку планшетов WPF в Surface 4 Pro?

Примечание. Несмотря на то, что сказано на отключить и включить драйвер сенсорного экрана, недостаточно отключить "HID-совместимые устройства с сенсорным экраном": до тех пор, пока "Intel(R) Precise touch devices" не будут отключены, сенсорный экран остается активированным, и большинство приложений WPF не работают.


person SERWare    schedule 28.07.2016    source источник


Ответы (1)


У меня была та же проблема, и я смог найти обходной путь, используя отражение.

Проблема вызвана тем, что WPF обновляет внутреннюю обработку планшетного устройства при отправке оконных сообщений WM_TABLET_ADDED, WM_TABLET_REMOVED или WM_DEVICECHANGED (см. .net referencesource). Поскольку эти сообщения могут генерироваться или не генерироваться в зависимости от используемого оборудования, исходного метода DisableWPFTabletSupport может быть достаточно или нет.

Мое решение состояло в том, чтобы обрабатывать и скрывать эти три оконных сообщения от WPF в дополнение к исходному коду:

class DisableWPFTouchAndStylus
{

private static void DisableWPFTabletSupport()
{
    // Get a collection of the tablet devices for this window.  
    var devices = Tablet.TabletDevices;

    if (devices.Count > 0)
    {
        // Get the Type of InputManager.
        var inputManagerType = typeof(InputManager);

        // Call the StylusLogic method on the InputManager.Current instance.
        var stylusLogic = inputManagerType.InvokeMember("StylusLogic",
                    BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
                    null, InputManager.Current, null);

        if (stylusLogic != null)
        {
            //  Get the type of the stylusLogic returned from the call to StylusLogic.
            var stylusLogicType = stylusLogic.GetType();

            // Loop until there are no more devices to remove.
            while (devices.Count > 0)
            {
                // Remove the first tablet device in the devices collection.
                stylusLogicType.InvokeMember("OnTabletRemoved",
                        BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic,
                        null, stylusLogic, new object[] { (uint)0 });
            }
        }
    }

    // END OF ORIGINAL CODE

    // hook into internal class SystemResources to keep it from updating the TabletDevices on system events

    object hwndWrapper = GetSystemResourcesHwnd();
    if (hwndWrapper != null)
    {
        // invoke hwndWrapper.AddHook( .. our method ..)
        var internalHwndWrapperType = hwndWrapper.GetType();

        // if the delegate is already set, we have already added the hook.
        if (_handleAndHideMessageDelegate == null)
        {
            // create the internal delegate that will hook into the window messages
            // need to hold a reference to that one, because internally the delegate is stored through a WeakReference object

            var internalHwndWrapperHookDelegate = internalHwndWrapperType.Assembly.GetType("MS.Win32.HwndWrapperHook");
            var handleAndHideMessagesHandle = typeof(DisableWPFTouchAndStylus).GetMethod(nameof(HandleAndHideMessages), BindingFlags.Static | BindingFlags.NonPublic);
            _handleAndHideMessageDelegate = Delegate.CreateDelegate(internalHwndWrapperHookDelegate, handleAndHideMessagesHandle);


            // add a delegate that handles WM_TABLET_ADD
            internalHwndWrapperType.InvokeMember("AddHook",
                BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public,
                null, hwndWrapper, new object[] { _handleAndHideMessageDelegate });
        }
    }
}

private static Delegate _handleAndHideMessageDelegate = null;

private static object GetSystemResourcesHwnd()
{
    var internalSystemResourcesType = typeof(Application).Assembly.GetType("System.Windows.SystemResources");

    // get HwndWrapper from internal property SystemRessources.Hwnd;
    var hwndWrapper = internalSystemResourcesType.InvokeMember("Hwnd",
                BindingFlags.GetProperty | BindingFlags.Static | BindingFlags.NonPublic,
                null, null, null);
    return hwndWrapper;
}

private static IntPtr HandleAndHideMessages(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    if (msg == (int)WindowMessage.WM_TABLET_ADDED ||
        msg == (int)WindowMessage.WM_TABLET_DELETED ||
        msg == (int)WindowMessage.WM_DEVICECHANGE)
    {
        handled = true;
    }
    return IntPtr.Zero;
}

enum WindowMessage : int
{
    WM_TABLET_DEFBASE = 0x02C0,
    WM_TABLET_ADDED = WM_TABLET_DEFBASE + 8,
    WM_TABLET_DELETED = WM_TABLET_DEFBASE + 9,
    WM_DEVICECHANGE = 0x0219
}

}

Некоторые примечания об этой реализации и ограничениях:

WPF не регистрируется для этих сообщений в приложениях MainWindow, а через скрытые окна с именем «SystemResources...», которые создаются для каждого экземпляра приложения. Таким образом, обработка этих сообщений в MainWindow (что было бы легко) здесь не поможет.

Мое решение также использует некоторое отражение и вызывает внутренние классы и внутренние свойства. Он работает для .net 4.6.2 и не тестировал его на более ранних версиях. Более того, при глубоком изучении исходного кода .net я также увидел два других возможных пути обновления обработки планшета, которые не обрабатываются в этом решении: конструкторы TabletCollection и HwndStylusInputProvider.

person tseifried    schedule 09.09.2016
comment
Вы тестировали на Surface 4 Pro? Спасибо, что поделились своим решением - person SERWare; 09.09.2016
comment
@SERWare: Да, это работает на SP4. Я не проверял его лично, но он работает на SP4 одного из наших клиентов. Чего я пока не знаю, работает ли он и на Surface Book, но я уверен. - person tseifried; 15.09.2016
comment
Я почти в таком же деле. Я обнаружил ошибку в клиентском пакете обновления 4 (SP4), и мне нужно подождать, пока он вернет его, чтобы протестировать ваше решение. Еще раз спасибо. - person SERWare; 16.09.2016
comment
Я попробовал ваше решение в проекте, ориентированном на 3.5 (я использую обновление 4 VS 2015), и ваш код успешно перехватывает делегата и пытается фильтровать сообщения. На данный момент я протестировал его на ПК без сенсорного экрана, но важно, чтобы он не давал сбоев при отсутствии сенсорного экрана, чтобы не поддерживать две версии. - person SERWare; 19.09.2016
comment
Я попробовал тестовое простое приложение WPF, которое использует ваш код на SP4, и сенсорный ввод не отключен. Делегат захватывает только одно сообщение при запуске приложения, но оно не фильтруется. - person SERWare; 20.09.2016
comment
@tseifried Это отличное решение для перехвата и блокировки WM_TABLET_ADDED, WM_TABLET_DELETED и WM_DEVICECHANGE на уровне приложения. Это решение отлично работало и для приложения .Net 4.6.2 на базе Win 10 WPF. Это помогло мне предотвратить зависание приложения WPF, когда сенсорное устройство дополнительного монитора было отключено и включено. Спасибо. - person Diwakar Padmaraja; 03.07.2019
comment
Куда мне вставить этот код? Как создать экземпляр объекта этого класса и как его использовать? - person sam; 23.03.2020