前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >WPF 触屏事件后触发鼠标事件的问题及 DataGrid 误触问题

WPF 触屏事件后触发鼠标事件的问题及 DataGrid 误触问题

作者头像
独立观察员
发布2022-12-06 19:03:24
2.6K0
发布2022-12-06 19:03:24
举报

WPF 触屏事件后触发鼠标事件的问题及 DataGrid 误触问题

目录

一、触屏事件连带触发鼠标事件的问题

二、DataGrid 误触问题及解决方法

独立观察员 2021 年 10 月 10 日

一、触屏事件连带触发鼠标事件的问题

这个是 WPF 已知的问题,网络上也有一些讨论,但是没有一个完美的方法来解决。本文也就是讲解其中的一种方法,亲测可行。

先来说说具体现象:触屏操作时,如果程序里使用了触屏事件(如:PreviewTouchDown、TouchDown、PreviewTouchUp、TouchUp),那么相应地会接着触发鼠标事件(PreviewMouseDown、MouseDown、PreviewMouseUp、MouseUp),这个据说是微软为了在触屏设备上兼容老程序,让这些程序能够接收从触屏事件转换来的鼠标事件,从而能正常工作。

所以,有一个说法是,只使用鼠标事件就行了,比如就单单使用 PreviewMouseDown 事件,或者按钮的话直接使用 Click 事件,或者使用命令(Command),这种方法理论上是可以的,但是实际情况下,有的时候会发现,这样用的话,触屏操作很不灵敏,可能要点好几次才触发。

这个触屏事件提升为鼠标事件的一个表现就是,触屏拖动或者点击,会在屏幕上 “残留” 鼠标,当然,是不可见的,或者表现为一个小星号。所以,从这个角度出发,产生了这样一种方法:点击后将鼠标移开。

这个方法能满足部分场景,比如之前有这样一个问题,在 DataGrid 表格上方有一个 DatePicker 日期选择控件,日期展开后,下拉的悬浮框会遮在表格上,当在下拉的悬浮框中选择日期后下拉框收起,这时却在表格上产生了某个条目的选中效果。针对于这个情况,就可以使用移开鼠标的方案,相关帮助类见下方链接:

https://gitee.com/dlgcy/WPFTemplateLib/blob/master/WpfHelpers/ClickAndTouchHelper2.cs

但是这次我遇到了一个 DataGrid 的误触问题,用移开鼠标的方法无效(也有可能是使用方法和时机不对),所以只能另寻它法。

注意,本文将在上篇文章《WPF DataGrid 通过自定义表头模拟首行固定》的示例程序基础上进行演示,建议先看看那篇文章。下面开始改造。

首先在行样式中添加了两个事件,一个是 PreviewTouchDown,另一个是 PreviewMouseDown:

触屏点击某一行,会先触发 PreviewTouchDown,然后触发 PreviewMouseDown,然后是行改变事件 SelectionChanged,最后依次是 PreviewTouchUp 和 PreviewMouseUp。带有 Preview 前缀的是隧道事件(可视为在事件前触发),没有的是冒泡事件(可视为在事件后触发,此处省略)。

那么如何去除触屏事件后连带引发鼠标事件的影响呢?通过在网络上苦苦搜索和尝试,在旧版的微软社区找到了一个可行的方法,帖子为《Prevent a WPF application to interpret touch events as mouse events?》(这个链接之后可能会访问不了)。

提问者就是为了解决触屏操作下触发鼠标事件的问题:

然后里面两个人分别给出了他们的解决方法,先来看看第一个:

这个就是本文采纳的方法,代码文字版如下:

代码语言:javascript
复制
public static class PreventTouchToMousePromotion
{
    public static void Register(FrameworkElement root)
    {
        root.PreviewMouseDown  = Evaluate;
        root.PreviewMouseMove  = Evaluate;
        root.PreviewMouseUp  = Evaluate;
    }


    private static void Evaluate(object sender, MouseEventArgs e)
    {
        //StylusDevice 属性,触屏操作连带触发时不为 null,鼠标触发时为 null;
        if (e.StylusDevice != null)
        {
            e.Handled = true; // 如果判断为 由触屏引发,则将事件标记为已处理;
        }
    }
}

再顺便看看第二个人的方法(没有去尝试,感兴趣的朋友可以试试):

二、DataGrid 误触问题及解决方法

上一个部分介绍了去除触屏事件后连带引发鼠标事件影响的方法,也就是通过鼠标事件参数的 StylusDevice 属性来判断是否是由触屏操作引发的(不为 null 则是触屏操作引发),进而进行处理。

然而,本次我实际上是要解决一个 DataGrid 表格在触屏下的误触问题,相关业务逻辑是在行改变事件(转为命令了)中的,本来是没有写 PreviewTouchDown 和 PreviewMouseDown 事件的(就是为了解决误触问题而引入),所以将鼠标事件标记为已处理(e.Handled = true;)的方法不能直接使用,还需要修改。原因是,行改变事件 SelectionChanged 是在 PreviewMouseDown 事件之后触发的,如果在 PreviewMouseDown 中将事件标记为已处理,那么行改变事件也就不会触发了。

首先来看看误触现象吧(动图):

也就是,我在行改变事件中加了个弹窗,询问用户是否要切换条目,如果选是的话,不作任何处理,如果选否的话,恢复之前的选中项。选是的时候不会有误触现象,选否的时候,鼠标操作的话也正常,而如果在弹窗时通过触屏点击了否,然后在界面空白处(这里是在右侧的信息区)触屏点击几下,就会在表格上,在之前点击要切换到的那一行上产生一个鼠标事件,而且没有触屏事件,这个不用怀疑,通过调试打断点很容易观察到。

关于点击几下会触发这个误触,我发现和屏幕支持几点触控有关。比如,公司的触摸屏支持 10 点触控,那么这里就是点击 10 下左右触发;我自己的一个小触摸屏,支持 5 点触控,这边则是在空白处点击 4 下触发。要查看屏幕支持几点触屏,可通过 GitHub 上的一个项目程序 ManipulationDemo 来查看(https://github.com/dotnet-campus/ManipulationDemo):

言归正传,从误触现象的动图中可以看到,已经能够判断出是否是误触了:

那么是怎么判断的呢?来看看代码:

代码语言:javascript
复制
private void EventSetter_PreviewTouchDown(object sender, TouchEventArgs e)
{
    // 真实触摸时会触发 PreviewTouchDown 事件,而误触时(点击弹窗取消后在空白处点击多次会误触表格)则不会(因为那个只触发鼠标事件);
    _vm.IsRealTouch = true;
}

/* 注意:触摸事件之后还会触发鼠标事件 */

private void EventSetter_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    //StylusDevice 属性,触屏操作连带触发时不为 null,鼠标触发时为 null
    if (e.StylusDevice != null)
    {// 触屏
        //e.Handled = true;
    }
    else
    {// 鼠标
        _vm.IsRealTouch = true; // 避免后续判断不正常;
    }
}

在 ViewModel 中新增了一个标记变量 IsRealTouch,用来记录是真实的触控或者鼠标点击意图,还是误触。真实触摸时会触发 PreviewTouchDown 事件,而误触时(点击弹窗取消后在空白处点击多次会误触表格)则不会(因为那个只触发鼠标事件),所以只要在鼠标事件 PreviewMouseDown 中能够判断出是否是触屏操作连带触发的就行了,而这个问题在前一部分已经解决了。所以,在触摸事件,以及鼠标事件的单纯鼠标触发的情况下,都对 IsRealTouch 赋值为 true 即可。

行改变事件(命令)中还需要给 IsRealTouch 复位,代码如下:

代码语言:javascript
复制
SelectionChangedCmd ??= new RelayCommand(o => IsCanSelectionChanged, o =>
{
    try
    {
        IsCanSelectionChanged = false;


        var args = o as SelectionChangedEventArgs;
        EditType = EditTypeEnum.Show;


        var isOk = MessageBox.Show($" 是否切换?(是否是误触?{!IsRealTouch})", "触屏误触问题演示", MessageBoxButton.YesNo);
        if (isOk == MessageBoxResult.No)
        {
            if (SelectedUser != _originUser)
            {
                SelectedUser = _originUser;
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }
    finally
    {
        IsRealTouch = false;
        _originUser = SelectedUser;


        IsCanSelectionChanged = true;
    }
});

可以看到,这样就能识别出是否是误触了。这里是演示,在实际使用时,识别到是误触,就可以直接返回而不用弹窗了。

问题解决了,那么原因呢?对于触屏操作产生鼠标事件,这个是微软为了兼容性而导致的,前面也说过了。至于为什么会有个触点残留在原来的位置,而且点击其它地方一定次数就会触发,这个问题我也没找到原因,请知道的朋友不吝赐教。有两个猜测,一是模态弹窗对事件有影响,一是命令对事件有影响,目前没想到怎么验证。

另外,之前说过弹窗点击是的情况下,后续没有误触现象,所以也有理由怀疑是从代码中改变了选中项(已绑定到 DataGrid 的选中项)所以会有这个问题。从代码中改变选中项又会触发行改变事件,所以加了个 IsCanSelectionChanged 来避免重入,当然,加不加这个避免重入的,都有误触现象。有点晕。

最后奉上源码地址:https://gitee.com/dlgcy/DLGCY_WPFPractice/tree/Blog20211010 ,大家可以帮忙研究研究。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-10-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 独立观察员博客 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、触屏事件连带触发鼠标事件的问题
  • 二、DataGrid 误触问题及解决方法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档