概述
Infinitecanvas 是一個 Canvas 控件,它支持無限畫布的滾動,支持 Ink,文本,格式文本,畫布縮放操作,撤銷重做操作,導入和導出數(shù)據(jù)。
這是一個非常實用的控件,在“來畫視頻” UWP 應用的繪畫功能中,也用到了這個控件,它對不同畫筆的選擇,橡皮擦,直尺和圓形尺,文字輸入和字體選擇等都提供了很便捷的支持,而且支持導入和導出數(shù)據(jù),可以很方便的創(chuàng)作繪畫作品,并能導出和分享給別人。?
下面是 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;
開發(fā)過程
代碼結(jié)構(gòu)分析
首先來看 InfiniteCanvas 的代碼結(jié)構(gòu),組成如下:
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 整體類結(jié)構(gòu)很清晰,每個類的功能也很明確,下面我們選取幾個重要的類來做分析。
1.??InfiniteCanvasCreateInkCommand
Command 文件夾的每一個類都比較簡單,我們找一個創(chuàng)建 Ink 命令看一下;該類實現(xiàn)了 IInfiniteCanvasCommand 這個接口,實現(xiàn)了 Execute() 和 Undo() 兩個方法;Command 初始化也很簡單,創(chuàng)建一個 InkDrawable 對象,初始化 drawableList 對象,執(zhí)行創(chuàng)建時加入該 drawable,撤銷時把它從 drawableList 中去掉。
代碼語言:JavaScript代碼運行次數(shù):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 的文本框控件,從下圖的結(jié)構(gòu)中可以看到完整的文本框?qū)傩远x方法,包括設置文字,設置編輯區(qū)域尺寸,文字變化的處理,光標位置的限制等。

來看一下判斷光標能夠下移一行的 CannotGoDown() 方法,按照換行符來切割文字行,如果只有一行則不可下移;當前選擇的結(jié)束,在最后一行時,也不可下移,其他情況都可以下移;
代碼語言:javascript代碼運行次數(shù):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 的渲染執(zhí)行和撤銷操作,計算渲染的尺寸空間,組織渲染的內(nèi)容。</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 中每個線條的每個點,加入到集合中,從點集合創(chuàng)建線條,最后生成新的 Stroke 列表;遍歷完成后,把新的 Stroke 列表用于 drawingSession 的 DrawInk 方法來實現(xiàn)繪制;?</p>代碼語言:javascript<i class="icon-code"></i>代碼運行次數(shù):<!-- -->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 方法實現(xiàn)繪制;根據(jù)字體大小設置橫向偏移 HorizontalMarginBasedOnFont,固定的縱向偏移 verticalMargin;
代碼語言:javascript代碼運行次數(shù):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 這個類中主要是實現(xiàn)?OnApplyTemplate(),DependencyProperty 處理,控件的定義,事件注冊,Canvas 的基礎事件處理等,InfiniteCanvas 中實現(xiàn)了一個 InkCanvas 所以可以實現(xiàn)各種筆觸的筆跡繪制;InfiniteCanvas.Events.cs 主要是 Canvas 中的各種按鈕點擊等事件處理;InfiniteCanvas.TextBox.cs 主要是 Canvas 中 TextBox 控件對應的控件定義和事件處理;
調(diào)用示例
InfiniteCanvas 控件的調(diào)用非常簡單,下面看看 XAML 中的調(diào)用:
代碼語言:javascript代碼運行次數(shù):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>
總結(jié)
到這里我們就把 Windows Community Toolkit 3.0 中的?InfiniteCanvas?的源代碼實現(xiàn)過程講解完成了,希望能對大家更好的理解和使用這個功能有所幫助。InfiniteCanvas 控件在繪畫類場景中有非常多的應用,控件默認實現(xiàn)了多種筆觸的繪畫,橡皮,文字,redo undo 等重要功能,開發(fā)者也可以根據(jù)?InfiniteCanvas 的實現(xiàn)自定義 Toolbar 的樣式和更多的繪畫筆觸,不同的筆畫保存方式等。
最后,再跟大家安利一下 WindowsCommunityToolkit 的官方微博:https://weibo.com/u/6506046490,?大家可以通過微博關(guān)注最新動態(tài)。
衷心感謝 WindowsCommunityToolkit 的作者們杰出的工作,感謝每一位貢獻者,Thank you so much, ALL WindowsCommunityToolkit AUTHORS !!!