欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

[WPF]为旧版本的应用添加触控支持

程序员文章站 2022-05-23 21:20:06
之前做WPF开发时曾经遇到这样一个需求:为一个基于 .NET Framework 3.5开发的老旧WPF程序添加触控支持,以便于大屏触控展示。 接手之后发现这是一个大坑。 项目最初的时候完全没考虑过软件架构设计,业务逻辑基本都写在后台代码中,经过两代程序员的开发维护(初代开发者已离职,文档这种东西不 ......

之前做wpf开发时曾经遇到这样一个需求:为一个基于 .net framework 3.5开发的老旧wpf程序添加触控支持,以便于大屏触控展示。

接手之后发现这是一个大坑。

项目最初的时候完全没考虑过软件架构设计,业务逻辑基本都写在后台代码中,经过两代程序员的开发维护(初代开发者已离职,文档这种东西不存在的),主界面cs代码已经有上万行,各种事件注册的非常杂乱。由于是做给*部门用的,稳定性很重要,修修补补不断的打补丁,程序已经非常难维护了。

而且不像最新.net框架下的wpf以及uwp开发中,我们有pointer开头的系列事件可以统一处理鼠标点击和触控。在基于.net框架 4.7以下版本构建的wpf应用里,鼠标点击和触控是独立的,需要分别处理。

这里有一点需要说明:在单点电阻式触控屏(除了atm机之类的特殊用途,基本要被淘汰掉了)下,系统对单点触控的处理是模拟的鼠标操作,这种情况下即使不处理触控事件,程序也可以正常运行,需要处理触控事件特指的是支持多点触控的电容式触摸屏。

当时我接手的wpf应用之前是完全没有做过触控事件处理的,我粗略的查找统计了一下,需要处理的按钮点击事件大概有上千个,如果手动处理,将是非常难以接受的重复工作,另外修改后的应用程序也必须完整走一遍测试流程,以防带来灾难性bug。

那么有没有一种简单的方法可以快速处理呢?

我们知道wpf开发中,所有的用户交互事件都是路由事件,其中带有preview前缀的为隧道路由事件,不带前缀的为冒泡路由事件。其区别是:隧道路由事件由根元素传递到触发事件的元素,而冒泡路由事件传递方向正好相反。那么,尽管程序中需要处理触控事件的地方很多,但是我们都可以在应用顶层元素中通过冒泡路由事件拦截到。是不是可以利用这一点做文章呢?

我的想法是这样的:由于应用已经处理了鼠标交互事件,那我们完全可以将应用的触控事件转发给鼠标交互事件的handler去处理,这样就避免了我们做机械的重复操作。

具体处理步骤如下:

  1. 在应用窗口的*元素(可视化树的根节点)上添加触控事件处理程序,捕获应用内部触控事件;

    this.addhandler(touchupevent, new routedeventhandler(gettouchup));
    this.addhandler(touchdownevent, new routedeventhandler(gettouchdown));
  2. 获取引发事件的源控件(原本想通过e.originalsource获取,但测试中发现获取的有错误,所以用uielement类中的inputhittest方法传入触控点坐标,获取到引发事件的源控件);

    toucheventargs te = (toucheventargs)e;
    point p = te.gettouchpoint(this).position;//这里是获取触控点相对某个界面元素的坐标
    uielement uicontrol = (uielement)this.inputhittest(p);
  3. 触发源控件的鼠标事件(在touchup中还同时触发了button类的click事件,用于处理按钮的点击事件);

    mousebuttoneventargs args = new mousebuttoneventargs(mouse.primarydevice,te.timestamp,mousebutton.left);
    args.routedevent = mousedownevent;
    uicontrol.raiseevent(args);

完整的事件处理代码如下:

        this.addhandler(touchupevent, new routedeventhandler(gettouchup));
        this.addhandler(touchdownevent, new routedeventhandler(gettouchdown));
        private void gettouchdown(object sender, routedeventargs e)
        {
            toucheventargs te = (toucheventargs)e;
            point p = te.gettouchpoint(this).position;
            uielement uicontrol = (uielement)this.inputhittest(p);
            mousebuttoneventargs args = new mousebuttoneventargs(mouse.primarydevice, te.timestamp, mousebutton.left);
            args.routedevent = mousedownevent;
            uicontrol.raiseevent(args);
        }
        private void gettouchup(object sender, routedeventargs e)
        {
            toucheventargs te = (toucheventargs)e;
            point p = te.gettouchpoint(this).position;
            uielement uicontrol = (uielement)this.inputhittest(p);
            mousebuttoneventargs args = new mousebuttoneventargs(mouse.primarydevice, te.timestamp, mousebutton.left);
            args.routedevent = mouseupevent;
            uicontrol.raiseevent(args);
            uicontrol.raiseevent(new routedeventargs(button.clickevent));
        }

要说明的一点是,我这里的处理是不完善的,仅仅处理了常见的点击操作。譬如鼠标右键(合理的触控事件应该是长按界面元素一段时间后触发),鼠标移动,滚轮操作都没有做处理,这些也是可以通过类似的方法转换为合适的触控事件触发的。

结尾

今天文章里所述的内容其实已经是很久以前的东西了,我现在的主要工作方向远离wpf开发很久了,突然翻相关的旧文件想起来,所以才有了这篇文章。好记性不如烂笔头,知识不用总有忘的一天,不如写出来贡献给需要的人,谢谢大家!