如何在 Surface 4 Pro 中禁用 WPF 平板电脑支持?
How to disable WPF Tablet Support in Surface 4 Pro?
我继承了一个面向 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-compliant touch screen devices" 是不够的:在 "Intel(R) Precise touch devices" 未禁用之前,触摸屏将保持激活状态,大多数 WPF 应用程序将失败。
我遇到了同样的问题,并且能够使用反射找到解决方法。
此问题是由于发送 window 消息 WM_TABLET_ADDED、WM_TABLET_REMOVED 或 WM_DEVICECHANGED 时 WPF 更新其内部平板电脑设备处理引起的(see .net referencesource ).由于这些消息可能生成也可能不生成,具体取决于所使用的硬件,因此原始的 DisableWPFTabletSupport 方法可能足够也可能不够。
除了原始代码之外,我的解决方案是处理和隐藏来自 WPF 的那三个 window 消息:
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..." 的隐藏 windows。因此,在 MainWindow 上处理这些消息(这很容易)在这里没有帮助。
我的解决方案还使用了相当多的反射和调用内部 类 和内部属性。它适用于 .net 4.6.2,尚未在早期版本上测试过。此外,在我深入研究 .net 源代码的过程中,我还看到了其他两个更新平板电脑处理的潜在路径,但在此解决方案中没有处理:TabletCollection 和 HwndStylusInputProvider 的构造函数。
我继承了一个面向 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-compliant touch screen devices" 是不够的:在 "Intel(R) Precise touch devices" 未禁用之前,触摸屏将保持激活状态,大多数 WPF 应用程序将失败。
我遇到了同样的问题,并且能够使用反射找到解决方法。
此问题是由于发送 window 消息 WM_TABLET_ADDED、WM_TABLET_REMOVED 或 WM_DEVICECHANGED 时 WPF 更新其内部平板电脑设备处理引起的(see .net referencesource ).由于这些消息可能生成也可能不生成,具体取决于所使用的硬件,因此原始的 DisableWPFTabletSupport 方法可能足够也可能不够。
除了原始代码之外,我的解决方案是处理和隐藏来自 WPF 的那三个 window 消息:
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..." 的隐藏 windows。因此,在 MainWindow 上处理这些消息(这很容易)在这里没有帮助。
我的解决方案还使用了相当多的反射和调用内部 类 和内部属性。它适用于 .net 4.6.2,尚未在早期版本上测试过。此外,在我深入研究 .net 源代码的过程中,我还看到了其他两个更新平板电脑处理的潜在路径,但在此解决方案中没有处理:TabletCollection 和 HwndStylusInputProvider 的构造函数。