前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >WPF 最简逻辑实现多指顺滑的笔迹书写

WPF 最简逻辑实现多指顺滑的笔迹书写

作者头像
林德熙
发布2020-08-24 11:56:20
发布2020-08-24 11:56:20
81900
代码可运行
举报
文章被收录于专栏:林德熙的博客林德熙的博客
运行总次数:0
代码可运行

只需不到 150 行代码就能实现一个支持多指顺滑的笔迹书写的应用。当然,这个应用除了笔迹书写外,没有其他任何功能。本文将不会使用 InkCanvas 而是使用更底的方法,通过 Stroke 进行绘制

这是我在写测试应用的时候,我想要了解我能用多少行代码实现一个多指顺滑的笔迹书写的核心逻辑。其实在 WPF 下,可以通过 Stroke 类的辅助,不断给 Stroke 添加点的方式,做到绘制出笔迹

绘制笔迹需要给定一个 DrawingAttributes 告诉笔迹的粗细和颜色等

其次需要创建 Stroke 类,在这个类的 StylusPoints 数组里面不断添加点,此时添加的点将会被加入到笔迹里面。在 WPF 的笔迹实际上算法就是将离散的点连接作为一段顺滑的笔迹

那么如何在界面显示出来?在 Stroke 类提供了 Draw 方法,可以绘制到 DrawingContext 里面

根据上面这些内容,咱写一个 StrokeVisual 继承 DrawingVisual 类

代码语言:javascript
代码运行次数:0
运行
复制
    /// <summary>
    ///     用于显示笔迹的类
    /// </summary>
    public class StrokeVisual : DrawingVisual
    {

    }

第一步就是拿到 DrawingAttributes 的值,可以使用如下代码

代码语言:javascript
代码运行次数:0
运行
复制
        /// <summary>
        ///     创建显示笔迹的类
        /// </summary>
        public StrokeVisual() : this(new DrawingAttributes()
        {
            Color = Colors.Red,
            FitToCurve = true,
            Width = 5
        })
        {
        }

        /// <summary>
        ///     创建显示笔迹的类
        /// </summary>
        /// <param name="drawingAttributes"></param>
        public StrokeVisual(DrawingAttributes drawingAttributes)
        {
            _drawingAttributes = drawingAttributes;
        }

        private readonly DrawingAttributes _drawingAttributes;

第二步就是实现不断添加点的功能

代码语言:javascript
代码运行次数:0
运行
复制
        /// <summary>
        ///     设置或获取显示的笔迹
        /// </summary>
        public Stroke Stroke { set; get; }

        /// <summary>
        ///     在笔迹中添加点
        /// </summary>
        /// <param name="point"></param>
        public void Add(StylusPoint point)
        {
            if (Stroke == null)
            {
                var collection = new StylusPointCollection {point};
                Stroke = new Stroke(collection) {DrawingAttributes = _drawingAttributes};
            }
            else
            {
                Stroke.StylusPoints.Add(point);
            }
        }

最后一步是让 Stroke 回执到 DrawingContext 里面。在 StrokeVisual 类,是继承 DrawingVisual 的,所以可以通过调用 RenderOpen 的方法实现

代码语言:javascript
代码运行次数:0
运行
复制
        /// <summary>
        ///     重新画出笔迹
        /// </summary>
        public void Redraw()
        {
            using var dc = RenderOpen();
            Stroke.Draw(dc);
        }

在拿到一个 Visual 类,也就是 StrokeVisual 可以如何在 WPF 中显示?最简单的方法是加一个自定义的类继承 FrameworkElement 来做,当然,在我自己的工具库里面是有默认实现的,请看代码

代码语言:javascript
代码运行次数:0
运行
复制
    public class VisualCanvas : FrameworkElement
    {
        protected override Visual GetVisualChild(int index)
        {
            return Visual;
        }

        protected override int VisualChildrenCount => 1;

        public VisualCanvas(DrawingVisual visual)
        {
            Visual = visual;
            AddVisualChild(visual);
        }

        public DrawingVisual Visual { get; }
    }

上面代码需要注意的有一点就是需要添加视觉树,通过 AddVisualChild 方法,否则加入的控件将只会被渲染一次。敲黑板,不在视觉树上的元素将不会持续渲染

接下来就是实现多指了,实现方式是通过 StylusMove 和 StylusUp 事件实现。每一个手指将会对应一个 StrokeVisual 类,因此 StrokeVisual 类只包含一条笔迹

通过 e.StylusDevice.Id 可以区分当前触摸的是哪个手指,通过写一个字典就能快速做到分开多个触摸

代码语言:javascript
代码运行次数:0
运行
复制
        private Dictionary<int, StrokeVisual> StrokeVisualList { get; } = new Dictionary<int, StrokeVisual>();

添加一个辅助方法,通过输入的 Id 返回一个 StrokeVisual 类,如果输入的 Id 不存在,也就是这是第一个按下,此时创建一个新的,同时加入到界面

代码语言:javascript
代码运行次数:0
运行
复制
        private StrokeVisual GetStrokeVisual(int id)
        {
            if (StrokeVisualList.TryGetValue(id, out var visual))
            {
                return visual;
            }

            var strokeVisual = new StrokeVisual();
            StrokeVisualList[id] = strokeVisual;
            var visualCanvas = new VisualCanvas(strokeVisual);
            Grid.Children.Add(visualCanvas);

            return strokeVisual;
        }

接下来就是在 StylusMove 的事件,拿到触摸点,传入到 StrokeVisual 类

代码语言:javascript
代码运行次数:0
运行
复制
        private void MainWindow_StylusMove(object sender, StylusEventArgs e)
        {
            var strokeVisual = GetStrokeVisual(e.StylusDevice.Id);
            var stylusPointCollection = e.GetStylusPoints(this);
            foreach (var stylusPoint in stylusPointCollection)
            {
                strokeVisual.Add(new StylusPoint(stylusPoint.X, stylusPoint.Y));
            }

            strokeVisual.Redraw();
        }

为什么使用 Stylus 事件,而不是 Touch 事件?原因有两个,第一个是 Stylus 是触笔,也就是触摸和笔都会进入。第二个是通过 GetStylusPoints 可以拿到密集的点集,此时绘制才能做到顺滑。那么为什么 GetStylusPoints 可以获取比 WM_Touch 更密集的点?原因是 GetStylusPoints 是通过 RealTime Stylus 实时触摸获取的点

最后一步就是在手指抬起的时候,删除字典的对应的值。因此触摸的 Id 是在相同时刻是不同的,但是取值只有0-255也就是最多画 255 画之后,将会存在至少一次 Id 的重复

代码语言:javascript
代码运行次数:0
运行
复制
        private void MainWindow_StylusUp(object sender, StylusEventArgs e)
        {
            StrokeVisualList.Remove(e.StylusDevice.Id);
        }

这样就实现了一个简单的多指顺滑的笔迹书写,但这不是一个高性能的书写方案。有啥可以做到虐次方案的性能的?有两个点,一个是输入一个是输出。这里的输入就是接收触摸,而输出就是渲染

拿到触摸最快的方法是通过 WPF 高性能笔 的 WPF 高速书写 StylusPlugIn 原理 方法拿到触摸点,简单的代码请看 WPF 最小的代码使用 DynamicRenderer 书写

而渲染部分,请看 高性能笔迹原理

渲染相对复杂,最简单的就是不要让 Stroke 包含太多的点,如果包含很多点,那么分为多个不同的 Stroke 对象,这样每次渲染的内容都不会很多,渲染性能相对比较高

本文的代码放在 github 欢迎小伙伴访问

但是无论如何做,都没有 UWP 的快。除非在 WPF 中上 Composition API 使用 Composition API 做高性能渲染 再加上 WPF 使用 Win2d 渲染的方法,使用 win2d 画出笔迹 和 win2d CanvasVirtualControl 存放绘制的笔迹

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档