【问题标题】:How to disable WPF Tablet Support in Surface 4 Pro?如何在 Surface 4 Pro 中禁用 WPF 平板电脑支持?
【发布时间】:2016-07-28 16:55:48
【问题描述】:

我继承了一个针对 Net 3.5 的 WPF 应用程序,我必须将它安装在 Surface Pro 4 (I5) 中。应用程序挂在不同的点上,我观察到动画有时永远不会触发完成的事件(也许它们会在某个点结束,但不是在 Duration 属性中表示的时间)。

作为转机,我尝试了Disable the RealTimeStylus for WPF Applications,但经过几次尝试,我注意到虽然 DisableWPFTabletSupport 方法已执行并完成(我在 DisableWPFTabletSupport 方法中添加了日志代码,并且在 Surface Pro 4 中删除了四个设备),可能是 WPF平板电脑支持在我的应用程序中仍然处于活动状态,因为该应用程序会不时挂起并继续捕获屏幕触摸。

因此,我发现能够在 Surface 4 Pro 中成功运行面向 Net 3.5 的 WPF 应用程序的唯一方法是使用 Windows 设备管理器在人机界面中禁用所有与触摸屏相关的设备。

有人知道如何在 Surface 4 Pro 中禁用 WPF 平板电脑支持吗?

注意。尽管disable and enable the touchscreen driver 上说了什么,但仅禁用“符合 HID 的触摸屏设备”是不够的:在未禁用“Intel(R) Precise touch devices”之前,触摸屏保持激活状态,大多数 WPF 应用程序将失败.

【问题讨论】:

    标签: c# .net wpf multi-touch pixelsense


    【解决方案1】:

    我遇到了同样的问题,并且能够使用反射找到解决方法。

    此问题是由 WPF 在发送窗口消息 WM_TABLET_ADDED、WM_TABLET_REMOVED 或 WM_DEVICECHANGED 时更新其内部平板设备处理引起的 (see .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 的构造函数。

    【讨论】:

    • 您在 Surface 4 pro 上进行过测试吗?感谢您分享您的解决方案
    • @SERWare:是的,它适用于 SP4。我没有亲自测试过它,但它适用于我们一位客户的 SP4。我还不知道它是否也适用于 Surface Book,但我有信心。
    • 我的情况差不多。我在客户的 SP4 上检测到错误,我必须等到他们将其退回来测试您的解决方案。再次感谢。
    • 我在一个针对 3.5 的项目中尝试了您的解决方案(我使用 VS 2015 更新 4),您的代码成功地挂钩了委托并尝试过滤消息。目前我在没有触摸屏的电脑上进行了测试,但重要的是在没有触摸屏的情况下它不会失败,以避免维护两个版本。
    • 我尝试了一个测试简单的 WPF 应用程序,该应用程序在 SP4 上使用您的代码并且触摸输入未被禁用。委托在应用程序启动时仅捕获一条消息,但未过滤。
    猜你喜欢
    • 2015-04-11
    • 2017-03-06
    • 1970-01-01
    • 2011-12-20
    • 2011-08-04
    • 1970-01-01
    • 2013-01-19
    • 1970-01-01
    • 2016-02-03
    相关资源
    最近更新 更多