概述
Infinitecanvas 是一个 Canvas 控件,它支持无限画布的滚动,支持 Ink,文本,格式文本,画布缩放操作,撤销重做操作,导入和导出数据。
这是一个非常实用的控件,在“来画视频” UWP 应用的绘画功能中,也用到了这个控件,它对不同画笔的选择,橡皮擦,直尺和圆形尺,文字输入和字体选择等都提供了很便捷的支持,而且支持导入和导出数据,可以很方便的创作绘画作品,并能导出和分享给别人。
下面是 Windows Community Toolkit Sample App 的示例截图和 code/doc 地址:

Windows Community Toolkit Doc – InfiniteCanvas
Windows Community Toolkit Source Code – InfiniteCanvas
Namespace: microsoft.Toolkit.Uwp.ui.Controls; Nuget: Microsoft.Toolkit.Uwp.UI.Controls;
开发过程
代码结构分析
首先来看 InfiniteCanvas 的代码结构,组成如下:
Commands – InfiniteCanvas 对应的所有命令,包括 redo/undo,Ink,Text 等,如上面示例图中 Toolbar 上所示;Controls – InfiniteCanvas 的主要控件都在这个文件夹里;Drawables – Text 和 Ink 主要的绘制功能在这个文件夹里;JsonConverters – 序列化和反序列化的主要功能,以及自定义的 Ink 属性等;InfiniteCanvas.Events.cs – InfiniteCanvas 的主要事件处理逻辑;InfiniteCanvas.TextBox.cs – InfiniteCanvas 添加文字的文本框控件处理逻辑;InfiniteCanvas.cs – InfiniteCanvas 控件的主要处理逻辑;InfiniteCanvas.xaml – InfiniteCanvas 控件的 XAML 样式文件;

InfiniteCanvas 整体类结构很清晰,每个类的功能也很明确,下面我们选取几个重要的类来做分析。
1. InfiniteCanvasCreateInkCommand
Command 文件夹的每一个类都比较简单,我们找一个创建 Ink 命令看一下;该类实现了 IInfiniteCanvasCommand 这个接口,实现了 Execute() 和 Undo() 两个方法;Command 初始化也很简单,创建一个 InkDrawable 对象,初始化 drawableList 对象,执行创建时加入该 drawable,撤销时把它从 drawableList 中去掉。
代码语言:JavaScript代码运行次数:0运行复制
internal class InfiniteCanvasCreateInkCommand : IInfiniteCanvasCommand{ private readonly List<idrawable> _drawableList; private readonly InkDrawable _drawable; public InfiniteCanvasCreateInkCommand(List<idrawable> drawableList, IReadOnlyList<inkstroke> strokes) { _drawable = new InkDrawable(strokes); _drawableList = drawableList; } public void Execute() { _drawableList.Add(_drawable); } public void Undo() { _drawableList.Remove(_drawable); }}</inkstroke></idrawable></idrawable>
2. InfiniteCanvasTextBox
用于 InfiniteCanvas 的文本框控件,从下图的结构中可以看到完整的文本框属性定义方法,包括设置文字,设置编辑区域尺寸,文字变化的处理,光标位置的限制等。

来看一下判断光标能够下移一行的 CannotGoDown() 方法,按照换行符来切割文字行,如果只有一行则不可下移;当前选择的结束,在最后一行时,也不可下移,其他情况都可以下移;
代码语言:javascript代码运行次数:0运行复制
internal bool CannotGoDown(){ var lines = _editZone.Text.Split('r'); if (lines.Count() == 1) { return true; } var lastLine = lines.ElementAt(lines.Length - 1); if ((_editZone.Text.Length - lastLine.Length) <p>3. InfiniteCanvasVirtualDrawingSurface</p><p>用于渲染 Ink 和 Text 的虚拟 drawing surface,它有几个部分类组成:</p><figure class=""><img src="https://img.php.cn/upload/article/001/503/042/174693481031747.jpg" alt="Windows Community Toolkit 3.0 - InfiniteCanvas"></figure><p>主要的处理逻辑,是利用 Commands 来操作 Ink 和 Text 的渲染执行和撤销操作,计算渲染的尺寸空间,组织渲染的内容。</p><p>4. InkDrawable 和 TextDrawable</p><p>Ink 和 Text 可绘画元素的绘画逻辑,分别来看一下两个类的组成:</p><figure class=""><img src="https://img.php.cn/upload/article/001/503/042/174693481057252.jpg" alt="Windows Community Toolkit 3.0 - InfiniteCanvas"></figure><figure class=""><img src="https://img.php.cn/upload/article/001/503/042/174693481061442.jpg" alt="Windows Community Toolkit 3.0 - InfiniteCanvas"></figure><p>我们看一下两个类的 Draw 方法:</p><p>Draw() - InkDrawable:</p><p>获得 Strokes 中每个线条的每个点,加入到集合中,从点集合创建线条,最后生成新的 Stroke 列表;遍历完成后,把新的 Stroke 列表用于 drawingSession 的 DrawInk 方法来实现绘制; </p>代码语言:javascript<i class="icon-code"></i>代码运行次数:<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" style="max-width:90%" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>复制<pre class="prism-token token line-numbers javascript">public void Draw(CanvasDrawingSession drawingSession, Rect sessionBounds){ var finalStrokeList = new List<inkstroke>(Strokes.Count); foreach (var stroke in Strokes) { var points = stroke.GetInkPoints(); var finalPointList = new List<inkpoint>(points.Count); foreach (var point in points) { finalPointList.Add(MapPointToToSessionBounds(point, sessionBounds)); } StrokeBuilder.SetDefaultDrawingAttributes(stroke.DrawingAttributes); var newStroke = StrokeBuilder.CreateStrokeFromInkPoints(finalPointList, stroke.PointTransform); finalStrokeList.Add(newStroke); } drawingSession.DrawInk(finalStrokeList);}</inkpoint></inkstroke>
Draw() – TextDrawble:
设置 Canvas 中文本的格式,使用文本和格式设置的 textLayout 来用于 drawingSession 的 DrawTextLayout 方法实现绘制;根据字体大小设置横向偏移 HorizontalMarginBasedOnFont,固定的纵向偏移 verticalMargin;
代码语言:javascript代码运行次数:0运行复制
public void Draw(CanvasDrawingSession drawingSession, Rect sessionBounds){ const int verticalMargin = 3; CanvasTextFormat format = new CanvasTextFormat { FontSize = FontSize, WordWrapping = CanvasWordWrapping.NoWrap, FontWeight = IsBold ? FontWeights.Bold : FontWeights.Normal, FontStyle = IsItalic ? FontStyle.Italic : FontStyle.Normal }; CanvasTextLayout textLayout = new CanvasTextLayout(drawingSession, Text, format, 0.0f, 0.0f); drawingSession.DrawTextLayout(textLayout, (float)(Bounds.X - sessionBounds.X + HorizontalMarginBasedOnFont), (float)(Bounds.Y - sessionBounds.Y + verticalMargin), TextColor);}
5. InfiniteCanvas
InfiniteCanvas 是主要逻辑处理,由 InfiniteCanvas.cs,InfiniteCanvas.Events.cs,InfiniteCanvas.TextBox.cs 三个部分类组成。
其中 InfiniteCanvas.cs 这个类中主要是实现 OnApplyTemplate(),DependencyProperty 处理,控件的定义,事件注册,Canvas 的基础事件处理等,InfiniteCanvas 中实现了一个 InkCanvas 所以可以实现各种笔触的笔迹绘制;InfiniteCanvas.Events.cs 主要是 Canvas 中的各种按钮点击等事件处理;InfiniteCanvas.TextBox.cs 主要是 Canvas 中 TextBox 控件对应的控件定义和事件处理;
调用示例
InfiniteCanvas 控件的调用非常简单,下面看看 XAML 中的调用:
代码语言:javascript代码运行次数:0运行复制
<page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:ignorable="d"><grid><infinitecanvas name="canvas" istoolbarvisible="True"></infinitecanvas></grid></page>
总结
到这里我们就把 Windows Community Toolkit 3.0 中的 InfiniteCanvas 的源代码实现过程讲解完成了,希望能对大家更好的理解和使用这个功能有所帮助。InfiniteCanvas 控件在绘画类场景中有非常多的应用,控件默认实现了多种笔触的绘画,橡皮,文字,redo undo 等重要功能,开发者也可以根据 InfiniteCanvas 的实现自定义 Toolbar 的样式和更多的绘画笔触,不同的笔画保存方式等。
最后,再跟大家安利一下 WindowsCommunityToolkit 的官方微博:https://weibo.com/u/6506046490, 大家可以通过微博关注最新动态。
衷心感谢 WindowsCommunityToolkit 的作者们杰出的工作,感谢每一位贡献者,Thank you so much, ALL WindowsCommunityToolkit AUTHORS !!!