前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >WPF 编写一个测试 WM_TOUCH 触摸消息延迟的应用

WPF 编写一个测试 WM_TOUCH 触摸消息延迟的应用

作者头像
林德熙
发布2023-04-07 09:03:16
5240
发布2023-04-07 09:03:16
举报
文章被收录于专栏:林德熙的博客林德熙的博客

我听说在 Win10 到 Win11 的系统版本左右,微软加上了一大波触摸性能优化,准确来说是 HID 性能优化。我想测试一下在这些系统下,采用从 Windows 消息接收到 WM_TOUCH 触摸消息的延迟将会是多少。本文将告诉大家我编写的测试应

为了能够让 WPF 窗口能接收到 WM_TOUCH 触摸消息,首先需要将 WPF 默认走的实时触摸机制禁用,否则两个触摸接收方法将会打架,在 Windows 层将不会调度 WM_TOUCH 触摸消息给到 WPF 窗口。根据 WPF 禁用实时触摸 提供的方法禁用实时触摸,如果没有禁用 WPF 的 RealTimeStylus 实时触摸,就无法拿到 WM_TOUCH 消息,这是因为两套触摸机制将会打架。在 Windows 系统层发现开启了实时触摸之后,将不会调度 WM_TOUCH 消息给到应用窗口

在 App 构造函数加上以下代码用来禁用 RealTimeStylus 实时触摸

代码语言:javascript
复制
public partial class App : Application
{
    public App()
    {
        AppContext.SetSwitch("Switch.System.Windows.Input.Stylus.DisableStylusAndTouchSupport", true);
    }
}

为了更加方便调用 Win32 函数,按照 dotnet 使用 CsWin32 库简化 Win32 函数调用逻辑 博客的方法,安装 Microsoft.Windows.CsWin32 库用来减少编写 PInvoke 的定义方法

这里采用 .NET 7 的 WPF 项目,可以编辑 csproj 用来安装 Microsoft.Windows.CsWin32 库,十分方便,修改 csproj 项目文件为以下代码

代码语言:javascript
复制
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net7.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Windows.CsWin32" PrivateAssets="all" Version="0.2.63-beta" />
  </ItemGroup>
</Project>

根据 dotnet 使用 CsWin32 库简化 Win32 函数调用逻辑 博客提到的方法,需要在项目新建一个名为 NativeMethods.txt 的文件,在此文件里面写入需要使用的 Win32 函数。此时 CsWin32 库将使用 SourceGenerator 技术生成这些写入的 Win32 函数的定义

下面是在 NativeMethods.txt 写入的代码

代码语言:javascript
复制
RegisterTouchWindow
GetMessageTime
GetTouchInputInfo

进入到 MainWindow 函数里面监听 SourceInitialized 事件。在 WPF 框架里面,约定了在 SourceInitialized 事件里就是创建完成了 Win32 窗口之后触发的,在此事件里面使用 Win32 窗口相关方法是安全的

代码语言:javascript
复制
    public MainWindow()
    {
        InitializeComponent();

        SourceInitialized += MainWindow_SourceInitialized;
    }

    private void MainWindow_SourceInitialized(object? sender, EventArgs e)
    {
    }

MainWindow_SourceInitialized 方法里面调用 RegisterTouchWindow 用来注册 WM_Touch 消息,代码如下

代码语言:javascript
复制
        var windowInteropHelper = new WindowInteropHelper(this);
        var hwnd = windowInteropHelper.Handle;

        // 如果启用了 TWF_WANTPALM ,则不会缓冲触摸输入中的数据包,并且不会在将数据包发送到应用程序之前执行手掌检测。 如果要在处理 WM_TOUCH 消息时实现最小延迟,则启用 TWF_WANTPALM 最有用
        PInvoke.RegisterTouchWindow(new HWND(hwnd), REGISTER_TOUCH_WINDOW_FLAGS.TWF_WANTPALM);

这里传入了 TWF_WANTPALM 参数,传入这个参数可以减少触摸消息延迟

接着根据 WPF 添加窗口消息钩子方法 博客接收 Windows 消息,代码如下

代码语言:javascript
复制
    private void MainWindow_SourceInitialized(object? sender, EventArgs e)
    {
        var windowInteropHelper = new WindowInteropHelper(this);
        var hwnd = windowInteropHelper.Handle;

        // 如果启用了 TWF_WANTPALM ,则不会缓冲触摸输入中的数据包,并且不会在将数据包发送到应用程序之前执行手掌检测。 如果要在处理 WM_TOUCH 消息时实现最小延迟,则启用 TWF_WANTPALM 最有用
        PInvoke.RegisterTouchWindow(new HWND(hwnd), REGISTER_TOUCH_WINDOW_FLAGS.TWF_WANTPALM);

        HwndSource source = HwndSource.FromHwnd(hwnd)!; // 这里在 SourceInitialized 一定是存在的
        source.AddHook(Hook);
    }

    private IntPtr Hook(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
    {
        // 忽略代码
    }

在 Hook 函数里面,判断收到的消息是否 WM_Touch 消息,如果是那就记录当前的消息时间,用来判断两条 WM_Touch 消息之间的延迟

代码语言:javascript
复制
    private IntPtr Hook(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
    {
        if (msg == WM_TOUCH)
        {
            // 触摸进来的
            var currentMessageTime = PInvoke.GetMessageTime();
            if (_lastTouchMessageTime != 0)
            {
                var delay = currentMessageTime - _lastTouchMessageTime;
                // 这就是消息的延迟
            }

            _lastTouchMessageTime = currentMessageTime;
        }

        return IntPtr.Zero;
    }

    private int _lastTouchMessageTime;

    private const int WM_TOUCH = 0x0240;

在自己的电脑上运行代码,即可用来测试 WM_Touch 触摸的延迟

我使用以上代码在我的 Demo 上测试和在我的一个复杂项目上测试,结果就是在 Demo 上的触摸延迟是 WM_Touch 和 RealTimeStylus 实时触摸几乎一样。但是在复杂的项目上,由于 Windows 消息太多或者是主线程忙碌,触摸延迟是 WM_Touch 比 RealTimeStylus 实时触摸大许多

本文的代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

代码语言:javascript
复制
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin f006732c9f97f370f5c063de024125b201313bd3

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码

代码语言:javascript
复制
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin f006732c9f97f370f5c063de024125b201313bd3

获取代码之后,进入 WalqujemjelNekokelhuwererere 文件夹

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
腾讯云服务器利旧
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档