颜才 发表于 2025-6-20 12:21:33

03 - LayoutPanels例子 - SimpleInkCanvas

C# maui暂时没有官方支持InkCanvas,但是不影响,自己实现一个就行了。目前支持画图,选择,移动和删除。同时支持自定义橡皮擦形状,也支持绑定自定义的形状列表。
实现一个Converter类,以后所有的绑定类型转换都在这个类中实现。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Shares.Utility
{
    public class Converter : IValueConverter
    {
   
    <local:Converter x:Key="Converter"/>public object? Convert(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
   
    <local:Converter x:Key="Converter"/>{
   
    <local:Converter x:Key="Converter"/>    // Implement conversion logic here
   
    <local:Converter x:Key="Converter"/>    if (value is List<string> list)
   
    <local:Converter x:Key="Converter"/>    {
   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>return string.Join(", ", list); // 自定义分隔符
   
    <local:Converter x:Key="Converter"/>    }
   
    <local:Converter x:Key="Converter"/>    else if (value is int intValue && targetType.IsEnum)
   
    <local:Converter x:Key="Converter"/>    {
   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>return Enum.ToObject(targetType, intValue); // 将整数转换为枚举类型
   
    <local:Converter x:Key="Converter"/>    }
   
    <local:Converter x:Key="Converter"/>    return value;
   
    <local:Converter x:Key="Converter"/>}
   
    <local:Converter x:Key="Converter"/>public object? ConvertBack(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
   
    <local:Converter x:Key="Converter"/>{
   
    <local:Converter x:Key="Converter"/>    // Implement conversion back logic here
   
    <local:Converter x:Key="Converter"/>    return value;
   
    <local:Converter x:Key="Converter"/>}
    }
}然后在MyStyles.xaml中添加Converter类的引用,这样以后所有项目都可以使用了,local是
xmlns:local="clr-namespace:Shares.Utility;assembly=Shares"
   
    <local:Converter x:Key="Converter"/>InkCanvas重写GraphicsView
    public class InkCanvas : GraphicsView, IDrawable    {   
    <local:Converter x:Key="Converter"/>public class DrawingPath   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    private RectF? cachedBounds;   
    <local:Converter x:Key="Converter"/>    private bool isDirty = true;   
    <local:Converter x:Key="Converter"/>    public Guid Id { get; } = Guid.NewGuid();   
    <local:Converter x:Key="Converter"/>    public PathF Path { get; set; } = new PathF();   
    <local:Converter x:Key="Converter"/>    public Color? StrokeColor { get; set; }   
    <local:Converter x:Key="Converter"/>    public float StrokeThickness { get; set; }   
    <local:Converter x:Key="Converter"/>    public bool IsSelected { get; set; }   
    <local:Converter x:Key="Converter"/>    public PointF Pos { get; set; }   
    <local:Converter x:Key="Converter"/>    public RectF Bounds   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>get   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    if (!isDirty && cachedBounds.HasValue)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>return cachedBounds.Value;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    if (Path.Count == 0)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>cachedBounds = RectF.Zero;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>return RectF.Zero;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    var points = Path.Points;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    float minX = float.MaxValue, minY = float.MaxValue;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    float maxX = float.MinValue, maxY = float.MinValue;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    foreach (var point in points)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>float x = point.X + Pos.X;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>float y = point.Y + Pos.Y;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>minX = Math.Min(minX, x);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>minY = Math.Min(minY, y);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>maxX = Math.Max(maxX, x);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>maxY = Math.Max(maxY, y);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    cachedBounds = new RectF(minX, minY, maxX - minX, maxY - minY);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    isDirty = false;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    return cachedBounds.Value;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>    public void InvalidateBounds() => isDirty = true;   
    <local:Converter x:Key="Converter"/>    public void LineTo(float x, float y)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>Path.LineTo(x, y);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>InvalidateBounds();   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>    public bool IntersectAt(PointF eraserPos, float eraserRadius)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>if (Path.Count == 0)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    return false;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>// 优化点接触检查   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>foreach (var point in Path.Points)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    float dx = point.X + Pos.X - eraserPos.X;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    float dy = point.Y + Pos.Y - eraserPos.Y;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    if (dx * dx + dy * dy = 2)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    var points = Path.Points;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    for (int i = 1; i < points.Count(); i++)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>var start = new PointF(points.ElementAt(i - 1).X + Pos.X, points.ElementAt(i - 1).Y + Pos.Y);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>var end = new PointF(points.ElementAt(i).X + Pos.X, points.ElementAt(i).Y + Pos.Y);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>if (PointToLineDistance(start, end, eraserPos) = 0 && minDistance1)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    var newPath = new DrawingPath   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>StrokeColor = StrokeColor,   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>StrokeThickness = StrokeThickness,   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>Pos = Pos   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    };   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    newPath.Path.MoveTo(points.ElementAt(1));   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    for (int i = 2; i < points.Count(); i++)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>newPath.Path.LineTo(points.ElementAt(i));   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    newPaths.Add(newPath);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>return newPaths;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    // 终点处理   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    if (bestIndex == points.Count() - 1)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>if (points.Count() > 1)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    var newPath = new DrawingPath   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>StrokeColor = StrokeColor,   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>StrokeThickness = StrokeThickness,   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>Pos = Pos   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    };   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    newPath.Path.MoveTo(points.ElementAt(0));   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    for (int i = 1; i < points.Count() - 1; i++)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>newPath.Path.LineTo(points.ElementAt(i));   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    newPaths.Add(newPath);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>return newPaths;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    // 中间点处理   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    if (bestIndex > 0 && bestIndex < points.Count() - 1)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>// 第一段路径   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>var path1 = new DrawingPath   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    StrokeColor = StrokeColor,   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    StrokeThickness = StrokeThickness,   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    Pos = Pos   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>};   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>path1.Path.MoveTo(points.ElementAt(0));   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>for (int i = 1; i0 && minDistance1)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>var path1 = new DrawingPath   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    StrokeColor = StrokeColor,   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    StrokeThickness = StrokeThickness,   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    Pos = Pos   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>};   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>path1.Path.MoveTo(points.ElementAt(0));   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>for (int i = 1; i < bestIndex; i++)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    path1.Path.LineTo(points.ElementAt(i));   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>newPaths.Add(path1);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    // 第二段路径   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    if (bestIndex < points.Count() - 1)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>var path2 = new DrawingPath   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    StrokeColor = StrokeColor,   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    StrokeThickness = StrokeThickness,   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    Pos = Pos   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>};   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>path2.Path.MoveTo(points.ElementAt(bestIndex));   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>for (int i = bestIndex + 1; i < points.Count(); i++)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    path2.Path.LineTo(points.ElementAt(i));   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>newPaths.Add(path2);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>return newPaths;   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>public enum InkCanvasEditingMode { Ink, Select, Erase }   
    <local:Converter x:Key="Converter"/>public static readonly BindableProperty EditingModeProperty =   
    <local:Converter x:Key="Converter"/>    BindableProperty.Create(nameof(EditingMode), typeof(InkCanvasEditingMode), typeof(InkCanvas),   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>InkCanvasEditingMode.Ink, BindingMode.TwoWay, propertyChanged: OnEditingModeChanged);   
    <local:Converter x:Key="Converter"/>private static void OnEditingModeChanged(BindableObject bindable, object oldValue, object newValue)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    if (bindable is InkCanvas canvas)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>canvas.ClearSelection();   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>canvas.Invalidate();   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>public InkCanvasEditingMode EditingMode   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    get => (InkCanvasEditingMode)GetValue(EditingModeProperty);   
    <local:Converter x:Key="Converter"/>    set => SetValue(EditingModeProperty, value);   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>public ObservableCollection Paths { get; set; } = new ObservableCollection();   
    <local:Converter x:Key="Converter"/>public DrawingPath Eraser { get; }   
    <local:Converter x:Key="Converter"/>public float EraserRadius { get; set; } = 15f; // 增大橡皮擦半径   
    <local:Converter x:Key="Converter"/>private DrawingPath? currentPath;   
    <local:Converter x:Key="Converter"/>private RectF? selectionRect;   
    <local:Converter x:Key="Converter"/>private PointF lastTouchPoint;   
    <local:Converter x:Key="Converter"/>private bool isMovingSelection;   
    <local:Converter x:Key="Converter"/>// 橡皮擦轨迹跟踪   
    <local:Converter x:Key="Converter"/>private readonly List eraserTrail = new List();   
    <local:Converter x:Key="Converter"/>private const int MaxEraserTrailPoints = 5;   
    <local:Converter x:Key="Converter"/>public Color StrokeColor { get; set; } = Colors.Black;   
    <local:Converter x:Key="Converter"/>public Color SelectionColor { get; set; } = Colors.Red;   
    <local:Converter x:Key="Converter"/>public float SelectionStrokeThickness { get; set; } = 1f;   
    <local:Converter x:Key="Converter"/>public float StrokeThickness { get; set; } = 1f;   
    <local:Converter x:Key="Converter"/>public InkCanvas()   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    Drawable = this;   
    <local:Converter x:Key="Converter"/>    BackgroundColor = Colors.Transparent;   
    <local:Converter x:Key="Converter"/>    Eraser = CreateEraserPath();   
    <local:Converter x:Key="Converter"/>    StartInteraction += OnTouchStarted;   
    <local:Converter x:Key="Converter"/>    DragInteraction += OnTouchMoved;   
    <local:Converter x:Key="Converter"/>    EndInteraction += OnTouchEnded;   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>private DrawingPath CreateEraserPath()   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    var path = new PathF();   
    <local:Converter x:Key="Converter"/>    var points = new[]   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>    new PointF(107.4f, 13), new PointF(113.7f, 28.8f),   
    <local:Converter x:Key="Converter"/>    new PointF(127.9f, 31.3f), new PointF(117.6f, 43.5f),   
    <local:Converter x:Key="Converter"/>    new PointF(120.1f, 60.8f), new PointF(107.4f, 52.6f),   
    <local:Converter x:Key="Converter"/>    new PointF(94.6f, 60.8f), new PointF(97.1f, 43.5f),   
    <local:Converter x:Key="Converter"/>    new PointF(86.8f, 31.3f), new PointF(101f, 28.8f)   
    <local:Converter x:Key="Converter"/>};   
    <local:Converter x:Key="Converter"/>    path.MoveTo(points);   
    <local:Converter x:Key="Converter"/>    for (int i = 1; i < points.Length; i++)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>path.LineTo(points);   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>    path.Close();   
    <local:Converter x:Key="Converter"/>    return new DrawingPath { Path = path, StrokeColor = Colors.Black, StrokeThickness = 1f };   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>private void OnTouchStarted(object? sender, TouchEventArgs e)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    if (e.Touches.Length == 0) return;   
    <local:Converter x:Key="Converter"/>    var point = e.Touches;   
    <local:Converter x:Key="Converter"/>    lastTouchPoint = new PointF(point.X, point.Y);   
    <local:Converter x:Key="Converter"/>    eraserTrail.Clear(); // 清除历史轨迹   
    <local:Converter x:Key="Converter"/>    switch (EditingMode)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Ink:   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    StartInking(lastTouchPoint);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    break;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Select:   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    StartSelection(lastTouchPoint);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    break;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Erase:   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    StartErase(lastTouchPoint);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    eraserTrail.Add(lastTouchPoint); // 添加起始点   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    break;   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>    Invalidate();   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>private void StartInking(PointF startPoint)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    currentPath = new DrawingPath   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>StrokeColor = StrokeColor,   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>StrokeThickness = StrokeThickness,   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>Pos = PointF.Zero   
    <local:Converter x:Key="Converter"/>    };   
    <local:Converter x:Key="Converter"/>    currentPath.Path.MoveTo(startPoint.X, startPoint.Y);   
    <local:Converter x:Key="Converter"/>    Paths.Add(currentPath);   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>private void StartSelection(PointF startPoint)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    isMovingSelection = Paths.Any(p => p.IsSelected && p.Bounds.Contains(startPoint));   
    <local:Converter x:Key="Converter"/>    if (!isMovingSelection)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>ClearSelection();   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>var clickedPath = Paths.LastOrDefault(p => p.Bounds.Contains(startPoint));   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>if (clickedPath != null)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    clickedPath.IsSelected = true;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    isMovingSelection = true;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>else   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    selectionRect = new RectF(startPoint, SizeF.Zero);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>private void StartErase(PointF startPoint)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    Eraser.Pos = new PointF(startPoint.X - Eraser.Path.Bounds.Width / 2,   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    startPoint.Y - Eraser.Path.Bounds.Height / 4);   
    <local:Converter x:Key="Converter"/>    Eraser.IsSelected = true;   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>private void OnTouchMoved(object? sender, TouchEventArgs e)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    if (e.Touches.Length == 0) return;   
    <local:Converter x:Key="Converter"/>    var currentPoint = new PointF(e.Touches.X, e.Touches.Y);   
    <local:Converter x:Key="Converter"/>    switch (EditingMode)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Ink:   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    ContinueInking(currentPoint);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    break;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Select:   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    UpdateSelection(currentPoint);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    break;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Erase:   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    UpdateEraser(currentPoint);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    ErasePaths();   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    break;   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>    Invalidate();   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>private void ContinueInking(PointF currentPoint)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    if (currentPath == null) return;   
    <local:Converter x:Key="Converter"/>    const float minDistance = 1.0f;   
    <local:Converter x:Key="Converter"/>    float dx = currentPoint.X - lastTouchPoint.X;   
    <local:Converter x:Key="Converter"/>    float dy = currentPoint.Y - lastTouchPoint.Y;   
    <local:Converter x:Key="Converter"/>    if (dx * dx + dy * dy > minDistance * minDistance)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>currentPath.LineTo(currentPoint.X, currentPoint.Y);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>lastTouchPoint = currentPoint;   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>private void UpdateSelection(PointF currentPoint)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    if (isMovingSelection)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>MoveSelectedPaths(currentPoint);   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>    else if (selectionRect.HasValue)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>UpdateSelectionRect(currentPoint);   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>private void UpdateEraser(PointF currentPoint)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    Eraser.Pos = new PointF(currentPoint.X - Eraser.Path.Bounds.Width / 2,   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    currentPoint.Y - Eraser.Path.Bounds.Height / 4);   
    <local:Converter x:Key="Converter"/>    // 添加到橡皮擦轨迹   
    <local:Converter x:Key="Converter"/>    eraserTrail.Add(Eraser.Pos);   
    <local:Converter x:Key="Converter"/>    if (eraserTrail.Count > MaxEraserTrailPoints)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>eraserTrail.RemoveAt(0);   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>    lastTouchPoint = currentPoint;   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>// 优化擦除逻辑   
    <local:Converter x:Key="Converter"/>private void ErasePaths()   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    // 倒序遍历所有路径   
    <local:Converter x:Key="Converter"/>    for (int i = Paths.Count - 1; i >= 0; i--)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>var path = Paths;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>// 检查橡皮擦轨迹上的所有点   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>foreach (var trailPoint in eraserTrail)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    if (path.IntersectAt(trailPoint, EraserRadius))   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>var newPaths = path.SplitAt(trailPoint, EraserRadius);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>if (newPaths.Count > 0)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    Paths.RemoveAt(i);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    foreach (var newPath in newPaths)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>if (newPath.Path.Count >= 2) // 只添加有效路径   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    Paths.Add(newPath);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    break; // 路径已被处理,跳出循环   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>else   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    // 没有新路径表示整个路径应被删除   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    Paths.RemoveAt(i);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    break;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>private void MoveSelectedPaths(PointF currentPoint)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    float deltaX = currentPoint.X - lastTouchPoint.X;   
    <local:Converter x:Key="Converter"/>    float deltaY = currentPoint.Y - lastTouchPoint.Y;   
    <local:Converter x:Key="Converter"/>    foreach (var path in Paths)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>if (path.IsSelected)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    path.Pos = new PointF(path.Pos.X + deltaX, path.Pos.Y + deltaY);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    path.InvalidateBounds();   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>    lastTouchPoint = currentPoint;   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>private void UpdateSelectionRect(PointF currentPoint)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    float x = Math.Min(lastTouchPoint.X, currentPoint.X);   
    <local:Converter x:Key="Converter"/>    float y = Math.Min(lastTouchPoint.Y, currentPoint.Y);   
    <local:Converter x:Key="Converter"/>    float width = Math.Abs(currentPoint.X - lastTouchPoint.X);   
    <local:Converter x:Key="Converter"/>    float height = Math.Abs(currentPoint.Y - lastTouchPoint.Y);   
    <local:Converter x:Key="Converter"/>    selectionRect = new RectF(x, y, width, height);   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>private void OnTouchEnded(object? sender, TouchEventArgs e)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    switch (EditingMode)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Select when selectionRect.HasValue:   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    FinalizeSelection();   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    break;   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>    currentPath = null;   
    <local:Converter x:Key="Converter"/>    selectionRect = null;   
    <local:Converter x:Key="Converter"/>    isMovingSelection = false;   
    <local:Converter x:Key="Converter"/>    Eraser.IsSelected = false;   
    <local:Converter x:Key="Converter"/>    eraserTrail.Clear(); // 清除橡皮擦轨迹   
    <local:Converter x:Key="Converter"/>    Invalidate();   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>private void FinalizeSelection()   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    var selection = selectionRect!.Value;   
    <local:Converter x:Key="Converter"/>    foreach (var path in Paths)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>if (!selection.IntersectsWith(path.Bounds)) continue;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>if (selection.Contains(path.Bounds))   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    path.IsSelected = true;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    continue;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>foreach (var point in path.Path.Points)   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    var absolutePoint = new PointF(point.X + path.Pos.X, point.Y + path.Pos.Y);   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    if (selection.Contains(absolutePoint))   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>path.IsSelected = true;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>break;   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>public void ClearSelection()   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    foreach (var path in Paths)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>path.IsSelected = false;   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>public void Draw(ICanvas canvas, RectF dirtyRect)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    canvas.FillColor = BackgroundColor;   
    <local:Converter x:Key="Converter"/>    canvas.FillRectangle(dirtyRect);   
    <local:Converter x:Key="Converter"/>    canvas.StrokeLineCap = LineCap.Round;   
    <local:Converter x:Key="Converter"/>    canvas.StrokeLineJoin = LineJoin.Round;   
    <local:Converter x:Key="Converter"/>    // 绘制所有路径   
    <local:Converter x:Key="Converter"/>    foreach (var path in Paths)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>DrawPath(canvas, path);   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>    // 绘制橡皮擦(如果被选中)   
    <local:Converter x:Key="Converter"/>    if (Eraser.IsSelected)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>DrawEraser(canvas);   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>    // 绘制选择框   
    <local:Converter x:Key="Converter"/>    if (selectionRect.HasValue)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>DrawSelectionRect(canvas, selectionRect.Value);   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>private void DrawPath(ICanvas canvas, DrawingPath path)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    var strokeColor = path.StrokeColor ?? Colors.Black;   
    <local:Converter x:Key="Converter"/>    float strokeSize = path.IsSelected ? path.StrokeThickness * 1.5f : path.StrokeThickness;   
    <local:Converter x:Key="Converter"/>    if (!path.IsSelected)   
    <local:Converter x:Key="Converter"/>    {   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>strokeColor = strokeColor.WithAlpha(0.5f);   
    <local:Converter x:Key="Converter"/>    }   
    <local:Converter x:Key="Converter"/>    canvas.StrokeColor = strokeColor;   
    <local:Converter x:Key="Converter"/>    canvas.StrokeSize = strokeSize;   
    <local:Converter x:Key="Converter"/>    canvas.SaveState();   
    <local:Converter x:Key="Converter"/>    canvas.Translate(path.Pos.X, path.Pos.Y);   
    <local:Converter x:Key="Converter"/>    canvas.DrawPath(path.Path);   
    <local:Converter x:Key="Converter"/>    canvas.RestoreState();   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>private void DrawEraser(ICanvas canvas)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    canvas.SaveState();   
    <local:Converter x:Key="Converter"/>    canvas.Translate(Eraser.Pos.X, Eraser.Pos.Y);   
    <local:Converter x:Key="Converter"/>    canvas.Scale(0.2f, 0.2f);   
    <local:Converter x:Key="Converter"/>    canvas.StrokeColor = Eraser.StrokeColor ?? Colors.Black;   
    <local:Converter x:Key="Converter"/>    canvas.StrokeSize = Eraser.StrokeThickness;   
    <local:Converter x:Key="Converter"/>    canvas.FillColor = Color.FromArgb("#FFD700");   
    <local:Converter x:Key="Converter"/>    canvas.FillPath(Eraser.Path);   
    <local:Converter x:Key="Converter"/>    canvas.DrawPath(Eraser.Path);   
    <local:Converter x:Key="Converter"/>    canvas.RestoreState();   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>private void DrawSelectionRect(ICanvas canvas, RectF rect)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    canvas.SaveState();   
    <local:Converter x:Key="Converter"/>    canvas.StrokeColor = SelectionColor;   
    <local:Converter x:Key="Converter"/>    canvas.StrokeSize = SelectionStrokeThickness;   
    <local:Converter x:Key="Converter"/>    canvas.StrokeDashPattern = new float[] { 5, 3 };   
    <local:Converter x:Key="Converter"/>    canvas.DrawRectangle(rect);   
    <local:Converter x:Key="Converter"/>    canvas.RestoreState();   
    <local:Converter x:Key="Converter"/>}   
    <local:Converter x:Key="Converter"/>// 静态工具方法   
    <local:Converter x:Key="Converter"/>public static float Distance(PointF a, PointF b)   
    <local:Converter x:Key="Converter"/>    => (float)Math.Sqrt(Math.Pow(a.X - b.X, 2) + Math.Pow(a.Y - b.Y, 2));   
    <local:Converter x:Key="Converter"/>public static float DistanceSquared(PointF a, PointF b)   
    <local:Converter x:Key="Converter"/>    => (a.X - b.X) * (a.X - b.X) + (a.Y - b.Y) * (a.Y - b.Y);   
    <local:Converter x:Key="Converter"/>public static float PointToLineDistance(PointF lineStart, PointF lineEnd, PointF point)   
    <local:Converter x:Key="Converter"/>{   
    <local:Converter x:Key="Converter"/>    float l2 = DistanceSquared(lineStart, lineEnd);   
    <local:Converter x:Key="Converter"/>    if (l2 == 0) return Distance(point, lineStart);   
    <local:Converter x:Key="Converter"/>    float t = Math.Max(0, Math.Min(1, Vector2.Dot(   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>new Vector2(point.X - lineStart.X, point.Y - lineStart.Y),   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>new Vector2(lineEnd.X - lineStart.X, lineEnd.Y - lineStart.Y)) / l2));   
    <local:Converter x:Key="Converter"/>    PointF projection = new PointF(   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>lineStart.X + t * (lineEnd.X - lineStart.X),   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>lineStart.Y + t * (lineEnd.Y - lineStart.Y)   
    <local:Converter x:Key="Converter"/>    );   
    <local:Converter x:Key="Converter"/>    return Distance(point, projection);   
    <local:Converter x:Key="Converter"/>}    }SimpleInkCanvas.xaml
   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>   
    <local:Converter x:Key="Converter"/>对应的cs代码
using static Shares.Utility.InkCanvas;namespace MauiViews.MauiDemos.Book._03;public partial class SimpleInkCanvas : ContentPage{        public SimpleInkCanvas()        {                InitializeComponent();                foreach (InkCanvasEditingMode mode in Enum.GetValues(typeof(InkCanvasEditingMode)))                {   
    <local:Converter x:Key="Converter"/>    lstEditingMode.Items.Add(mode.ToString());                        lstEditingMode.SelectedIndex = 0;   
    <local:Converter x:Key="Converter"/>}    }}运行效果

 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 03 - LayoutPanels例子 - SimpleInkCanvas