找回密码
 立即注册
首页 业界区 业界 03 - LayoutPanels例子 - SimpleInkCanvas

03 - LayoutPanels例子 - SimpleInkCanvas

颜才 2025-6-20 12:21:33
C# maui暂时没有官方支持InkCanvas,但是不影响,自己实现一个就行了。目前支持画图,选择,移动和删除。同时支持自定义橡皮擦形状,也支持绑定自定义的形状列表。
实现一个Converter类,以后所有的绑定类型转换都在这个类中实现。
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace Shares.Utility
  8. {
  9.     public class Converter : IValueConverter
  10.     {
  11.    
  12.     <local:Converter x:Key="Converter"/>public object? Convert(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
  13.    
  14.     <local:Converter x:Key="Converter"/>{
  15.    
  16.     <local:Converter x:Key="Converter"/>    // Implement conversion logic here
  17.    
  18.     <local:Converter x:Key="Converter"/>    if (value is List<string> list)
  19.    
  20.     <local:Converter x:Key="Converter"/>    {
  21.    
  22.     <local:Converter x:Key="Converter"/>   
  23.     <local:Converter x:Key="Converter"/>return string.Join(", ", list); // 自定义分隔符
  24.    
  25.     <local:Converter x:Key="Converter"/>    }
  26.    
  27.     <local:Converter x:Key="Converter"/>    else if (value is int intValue && targetType.IsEnum)
  28.    
  29.     <local:Converter x:Key="Converter"/>    {
  30.    
  31.     <local:Converter x:Key="Converter"/>   
  32.     <local:Converter x:Key="Converter"/>return Enum.ToObject(targetType, intValue); // 将整数转换为枚举类型
  33.    
  34.     <local:Converter x:Key="Converter"/>    }
  35.    
  36.     <local:Converter x:Key="Converter"/>    return value;
  37.    
  38.     <local:Converter x:Key="Converter"/>}
  39.    
  40.     <local:Converter x:Key="Converter"/>public object? ConvertBack(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
  41.    
  42.     <local:Converter x:Key="Converter"/>{
  43.    
  44.     <local:Converter x:Key="Converter"/>    // Implement conversion back logic here
  45.    
  46.     <local:Converter x:Key="Converter"/>    return value;
  47.    
  48.     <local:Converter x:Key="Converter"/>}
  49.     }
  50. }
复制代码
然后在MyStyles.xaml中添加Converter类的引用,这样以后所有项目都可以使用了,local是
xmlns:local="clr-namespace:Shares.Utility;assembly=Shares"
  1.    
  2.     <local:Converter x:Key="Converter"/>
复制代码
InkCanvas重写GraphicsView
  1.     public class InkCanvas : GraphicsView, IDrawable    {   
  2.     <local:Converter x:Key="Converter"/>public class DrawingPath   
  3.     <local:Converter x:Key="Converter"/>{   
  4.     <local:Converter x:Key="Converter"/>    private RectF? cachedBounds;   
  5.     <local:Converter x:Key="Converter"/>    private bool isDirty = true;   
  6.     <local:Converter x:Key="Converter"/>    public Guid Id { get; } = Guid.NewGuid();   
  7.     <local:Converter x:Key="Converter"/>    public PathF Path { get; set; } = new PathF();   
  8.     <local:Converter x:Key="Converter"/>    public Color? StrokeColor { get; set; }   
  9.     <local:Converter x:Key="Converter"/>    public float StrokeThickness { get; set; }   
  10.     <local:Converter x:Key="Converter"/>    public bool IsSelected { get; set; }   
  11.     <local:Converter x:Key="Converter"/>    public PointF Pos { get; set; }   
  12.     <local:Converter x:Key="Converter"/>    public RectF Bounds   
  13.     <local:Converter x:Key="Converter"/>    {   
  14.     <local:Converter x:Key="Converter"/>   
  15.     <local:Converter x:Key="Converter"/>get   
  16.     <local:Converter x:Key="Converter"/>   
  17.     <local:Converter x:Key="Converter"/>{   
  18.     <local:Converter x:Key="Converter"/>   
  19.     <local:Converter x:Key="Converter"/>    if (!isDirty && cachedBounds.HasValue)   
  20.     <local:Converter x:Key="Converter"/>   
  21.     <local:Converter x:Key="Converter"/>   
  22.     <local:Converter x:Key="Converter"/>return cachedBounds.Value;   
  23.     <local:Converter x:Key="Converter"/>   
  24.     <local:Converter x:Key="Converter"/>    if (Path.Count == 0)   
  25.     <local:Converter x:Key="Converter"/>   
  26.     <local:Converter x:Key="Converter"/>    {   
  27.     <local:Converter x:Key="Converter"/>   
  28.     <local:Converter x:Key="Converter"/>   
  29.     <local:Converter x:Key="Converter"/>cachedBounds = RectF.Zero;   
  30.     <local:Converter x:Key="Converter"/>   
  31.     <local:Converter x:Key="Converter"/>   
  32.     <local:Converter x:Key="Converter"/>return RectF.Zero;   
  33.     <local:Converter x:Key="Converter"/>   
  34.     <local:Converter x:Key="Converter"/>    }   
  35.     <local:Converter x:Key="Converter"/>   
  36.     <local:Converter x:Key="Converter"/>    var points = Path.Points;   
  37.     <local:Converter x:Key="Converter"/>   
  38.     <local:Converter x:Key="Converter"/>    float minX = float.MaxValue, minY = float.MaxValue;   
  39.     <local:Converter x:Key="Converter"/>   
  40.     <local:Converter x:Key="Converter"/>    float maxX = float.MinValue, maxY = float.MinValue;   
  41.     <local:Converter x:Key="Converter"/>   
  42.     <local:Converter x:Key="Converter"/>    foreach (var point in points)   
  43.     <local:Converter x:Key="Converter"/>   
  44.     <local:Converter x:Key="Converter"/>    {   
  45.     <local:Converter x:Key="Converter"/>   
  46.     <local:Converter x:Key="Converter"/>   
  47.     <local:Converter x:Key="Converter"/>float x = point.X + Pos.X;   
  48.     <local:Converter x:Key="Converter"/>   
  49.     <local:Converter x:Key="Converter"/>   
  50.     <local:Converter x:Key="Converter"/>float y = point.Y + Pos.Y;   
  51.     <local:Converter x:Key="Converter"/>   
  52.     <local:Converter x:Key="Converter"/>   
  53.     <local:Converter x:Key="Converter"/>minX = Math.Min(minX, x);   
  54.     <local:Converter x:Key="Converter"/>   
  55.     <local:Converter x:Key="Converter"/>   
  56.     <local:Converter x:Key="Converter"/>minY = Math.Min(minY, y);   
  57.     <local:Converter x:Key="Converter"/>   
  58.     <local:Converter x:Key="Converter"/>   
  59.     <local:Converter x:Key="Converter"/>maxX = Math.Max(maxX, x);   
  60.     <local:Converter x:Key="Converter"/>   
  61.     <local:Converter x:Key="Converter"/>   
  62.     <local:Converter x:Key="Converter"/>maxY = Math.Max(maxY, y);   
  63.     <local:Converter x:Key="Converter"/>   
  64.     <local:Converter x:Key="Converter"/>    }   
  65.     <local:Converter x:Key="Converter"/>   
  66.     <local:Converter x:Key="Converter"/>    cachedBounds = new RectF(minX, minY, maxX - minX, maxY - minY);   
  67.     <local:Converter x:Key="Converter"/>   
  68.     <local:Converter x:Key="Converter"/>    isDirty = false;   
  69.     <local:Converter x:Key="Converter"/>   
  70.     <local:Converter x:Key="Converter"/>    return cachedBounds.Value;   
  71.     <local:Converter x:Key="Converter"/>   
  72.     <local:Converter x:Key="Converter"/>}   
  73.     <local:Converter x:Key="Converter"/>    }   
  74.     <local:Converter x:Key="Converter"/>    public void InvalidateBounds() => isDirty = true;   
  75.     <local:Converter x:Key="Converter"/>    public void LineTo(float x, float y)   
  76.     <local:Converter x:Key="Converter"/>    {   
  77.     <local:Converter x:Key="Converter"/>   
  78.     <local:Converter x:Key="Converter"/>Path.LineTo(x, y);   
  79.     <local:Converter x:Key="Converter"/>   
  80.     <local:Converter x:Key="Converter"/>InvalidateBounds();   
  81.     <local:Converter x:Key="Converter"/>    }   
  82.     <local:Converter x:Key="Converter"/>    public bool IntersectAt(PointF eraserPos, float eraserRadius)   
  83.     <local:Converter x:Key="Converter"/>    {   
  84.     <local:Converter x:Key="Converter"/>   
  85.     <local:Converter x:Key="Converter"/>if (Path.Count == 0)   
  86.     <local:Converter x:Key="Converter"/>   
  87.     <local:Converter x:Key="Converter"/>    return false;   
  88.     <local:Converter x:Key="Converter"/>   
  89.     <local:Converter x:Key="Converter"/>// 优化点接触检查   
  90.     <local:Converter x:Key="Converter"/>   
  91.     <local:Converter x:Key="Converter"/>foreach (var point in Path.Points)   
  92.     <local:Converter x:Key="Converter"/>   
  93.     <local:Converter x:Key="Converter"/>{   
  94.     <local:Converter x:Key="Converter"/>   
  95.     <local:Converter x:Key="Converter"/>    float dx = point.X + Pos.X - eraserPos.X;   
  96.     <local:Converter x:Key="Converter"/>   
  97.     <local:Converter x:Key="Converter"/>    float dy = point.Y + Pos.Y - eraserPos.Y;   
  98.     <local:Converter x:Key="Converter"/>   
  99.     <local:Converter x:Key="Converter"/>    if (dx * dx + dy * dy = 2)   
  100.     <local:Converter x:Key="Converter"/>   
  101.     <local:Converter x:Key="Converter"/>{   
  102.     <local:Converter x:Key="Converter"/>   
  103.     <local:Converter x:Key="Converter"/>    var points = Path.Points;   
  104.     <local:Converter x:Key="Converter"/>   
  105.     <local:Converter x:Key="Converter"/>    for (int i = 1; i < points.Count(); i++)   
  106.     <local:Converter x:Key="Converter"/>   
  107.     <local:Converter x:Key="Converter"/>    {   
  108.     <local:Converter x:Key="Converter"/>   
  109.     <local:Converter x:Key="Converter"/>   
  110.     <local:Converter x:Key="Converter"/>var start = new PointF(points.ElementAt(i - 1).X + Pos.X, points.ElementAt(i - 1).Y + Pos.Y);   
  111.     <local:Converter x:Key="Converter"/>   
  112.     <local:Converter x:Key="Converter"/>   
  113.     <local:Converter x:Key="Converter"/>var end = new PointF(points.ElementAt(i).X + Pos.X, points.ElementAt(i).Y + Pos.Y);   
  114.     <local:Converter x:Key="Converter"/>   
  115.     <local:Converter x:Key="Converter"/>   
  116.     <local:Converter x:Key="Converter"/>if (PointToLineDistance(start, end, eraserPos) = 0 && minDistance  1)   
  117.     <local:Converter x:Key="Converter"/>   
  118.     <local:Converter x:Key="Converter"/>   
  119.     <local:Converter x:Key="Converter"/>{   
  120.     <local:Converter x:Key="Converter"/>   
  121.     <local:Converter x:Key="Converter"/>   
  122.     <local:Converter x:Key="Converter"/>    var newPath = new DrawingPath   
  123.     <local:Converter x:Key="Converter"/>   
  124.     <local:Converter x:Key="Converter"/>   
  125.     <local:Converter x:Key="Converter"/>    {   
  126.     <local:Converter x:Key="Converter"/>   
  127.     <local:Converter x:Key="Converter"/>   
  128.     <local:Converter x:Key="Converter"/>   
  129.     <local:Converter x:Key="Converter"/>StrokeColor = StrokeColor,   
  130.     <local:Converter x:Key="Converter"/>   
  131.     <local:Converter x:Key="Converter"/>   
  132.     <local:Converter x:Key="Converter"/>   
  133.     <local:Converter x:Key="Converter"/>StrokeThickness = StrokeThickness,   
  134.     <local:Converter x:Key="Converter"/>   
  135.     <local:Converter x:Key="Converter"/>   
  136.     <local:Converter x:Key="Converter"/>   
  137.     <local:Converter x:Key="Converter"/>Pos = Pos   
  138.     <local:Converter x:Key="Converter"/>   
  139.     <local:Converter x:Key="Converter"/>   
  140.     <local:Converter x:Key="Converter"/>    };   
  141.     <local:Converter x:Key="Converter"/>   
  142.     <local:Converter x:Key="Converter"/>   
  143.     <local:Converter x:Key="Converter"/>    newPath.Path.MoveTo(points.ElementAt(1));   
  144.     <local:Converter x:Key="Converter"/>   
  145.     <local:Converter x:Key="Converter"/>   
  146.     <local:Converter x:Key="Converter"/>    for (int i = 2; i < points.Count(); i++)   
  147.     <local:Converter x:Key="Converter"/>   
  148.     <local:Converter x:Key="Converter"/>   
  149.     <local:Converter x:Key="Converter"/>    {   
  150.     <local:Converter x:Key="Converter"/>   
  151.     <local:Converter x:Key="Converter"/>   
  152.     <local:Converter x:Key="Converter"/>   
  153.     <local:Converter x:Key="Converter"/>newPath.Path.LineTo(points.ElementAt(i));   
  154.     <local:Converter x:Key="Converter"/>   
  155.     <local:Converter x:Key="Converter"/>   
  156.     <local:Converter x:Key="Converter"/>    }   
  157.     <local:Converter x:Key="Converter"/>   
  158.     <local:Converter x:Key="Converter"/>   
  159.     <local:Converter x:Key="Converter"/>    newPaths.Add(newPath);   
  160.     <local:Converter x:Key="Converter"/>   
  161.     <local:Converter x:Key="Converter"/>   
  162.     <local:Converter x:Key="Converter"/>}   
  163.     <local:Converter x:Key="Converter"/>   
  164.     <local:Converter x:Key="Converter"/>   
  165.     <local:Converter x:Key="Converter"/>return newPaths;   
  166.     <local:Converter x:Key="Converter"/>   
  167.     <local:Converter x:Key="Converter"/>    }   
  168.     <local:Converter x:Key="Converter"/>   
  169.     <local:Converter x:Key="Converter"/>    // 终点处理   
  170.     <local:Converter x:Key="Converter"/>   
  171.     <local:Converter x:Key="Converter"/>    if (bestIndex == points.Count() - 1)   
  172.     <local:Converter x:Key="Converter"/>   
  173.     <local:Converter x:Key="Converter"/>    {   
  174.     <local:Converter x:Key="Converter"/>   
  175.     <local:Converter x:Key="Converter"/>   
  176.     <local:Converter x:Key="Converter"/>if (points.Count() > 1)   
  177.     <local:Converter x:Key="Converter"/>   
  178.     <local:Converter x:Key="Converter"/>   
  179.     <local:Converter x:Key="Converter"/>{   
  180.     <local:Converter x:Key="Converter"/>   
  181.     <local:Converter x:Key="Converter"/>   
  182.     <local:Converter x:Key="Converter"/>    var newPath = new DrawingPath   
  183.     <local:Converter x:Key="Converter"/>   
  184.     <local:Converter x:Key="Converter"/>   
  185.     <local:Converter x:Key="Converter"/>    {   
  186.     <local:Converter x:Key="Converter"/>   
  187.     <local:Converter x:Key="Converter"/>   
  188.     <local:Converter x:Key="Converter"/>   
  189.     <local:Converter x:Key="Converter"/>StrokeColor = StrokeColor,   
  190.     <local:Converter x:Key="Converter"/>   
  191.     <local:Converter x:Key="Converter"/>   
  192.     <local:Converter x:Key="Converter"/>   
  193.     <local:Converter x:Key="Converter"/>StrokeThickness = StrokeThickness,   
  194.     <local:Converter x:Key="Converter"/>   
  195.     <local:Converter x:Key="Converter"/>   
  196.     <local:Converter x:Key="Converter"/>   
  197.     <local:Converter x:Key="Converter"/>Pos = Pos   
  198.     <local:Converter x:Key="Converter"/>   
  199.     <local:Converter x:Key="Converter"/>   
  200.     <local:Converter x:Key="Converter"/>    };   
  201.     <local:Converter x:Key="Converter"/>   
  202.     <local:Converter x:Key="Converter"/>   
  203.     <local:Converter x:Key="Converter"/>    newPath.Path.MoveTo(points.ElementAt(0));   
  204.     <local:Converter x:Key="Converter"/>   
  205.     <local:Converter x:Key="Converter"/>   
  206.     <local:Converter x:Key="Converter"/>    for (int i = 1; i < points.Count() - 1; i++)   
  207.     <local:Converter x:Key="Converter"/>   
  208.     <local:Converter x:Key="Converter"/>   
  209.     <local:Converter x:Key="Converter"/>    {   
  210.     <local:Converter x:Key="Converter"/>   
  211.     <local:Converter x:Key="Converter"/>   
  212.     <local:Converter x:Key="Converter"/>   
  213.     <local:Converter x:Key="Converter"/>newPath.Path.LineTo(points.ElementAt(i));   
  214.     <local:Converter x:Key="Converter"/>   
  215.     <local:Converter x:Key="Converter"/>   
  216.     <local:Converter x:Key="Converter"/>    }   
  217.     <local:Converter x:Key="Converter"/>   
  218.     <local:Converter x:Key="Converter"/>   
  219.     <local:Converter x:Key="Converter"/>    newPaths.Add(newPath);   
  220.     <local:Converter x:Key="Converter"/>   
  221.     <local:Converter x:Key="Converter"/>   
  222.     <local:Converter x:Key="Converter"/>}   
  223.     <local:Converter x:Key="Converter"/>   
  224.     <local:Converter x:Key="Converter"/>   
  225.     <local:Converter x:Key="Converter"/>return newPaths;   
  226.     <local:Converter x:Key="Converter"/>   
  227.     <local:Converter x:Key="Converter"/>    }   
  228.     <local:Converter x:Key="Converter"/>   
  229.     <local:Converter x:Key="Converter"/>    // 中间点处理   
  230.     <local:Converter x:Key="Converter"/>   
  231.     <local:Converter x:Key="Converter"/>    if (bestIndex > 0 && bestIndex < points.Count() - 1)   
  232.     <local:Converter x:Key="Converter"/>   
  233.     <local:Converter x:Key="Converter"/>    {   
  234.     <local:Converter x:Key="Converter"/>   
  235.     <local:Converter x:Key="Converter"/>   
  236.     <local:Converter x:Key="Converter"/>// 第一段路径   
  237.     <local:Converter x:Key="Converter"/>   
  238.     <local:Converter x:Key="Converter"/>   
  239.     <local:Converter x:Key="Converter"/>var path1 = new DrawingPath   
  240.     <local:Converter x:Key="Converter"/>   
  241.     <local:Converter x:Key="Converter"/>   
  242.     <local:Converter x:Key="Converter"/>{   
  243.     <local:Converter x:Key="Converter"/>   
  244.     <local:Converter x:Key="Converter"/>   
  245.     <local:Converter x:Key="Converter"/>    StrokeColor = StrokeColor,   
  246.     <local:Converter x:Key="Converter"/>   
  247.     <local:Converter x:Key="Converter"/>   
  248.     <local:Converter x:Key="Converter"/>    StrokeThickness = StrokeThickness,   
  249.     <local:Converter x:Key="Converter"/>   
  250.     <local:Converter x:Key="Converter"/>   
  251.     <local:Converter x:Key="Converter"/>    Pos = Pos   
  252.     <local:Converter x:Key="Converter"/>   
  253.     <local:Converter x:Key="Converter"/>   
  254.     <local:Converter x:Key="Converter"/>};   
  255.     <local:Converter x:Key="Converter"/>   
  256.     <local:Converter x:Key="Converter"/>   
  257.     <local:Converter x:Key="Converter"/>path1.Path.MoveTo(points.ElementAt(0));   
  258.     <local:Converter x:Key="Converter"/>   
  259.     <local:Converter x:Key="Converter"/>   
  260.     <local:Converter x:Key="Converter"/>for (int i = 1; i  0 && minDistance  1)   
  261.     <local:Converter x:Key="Converter"/>   
  262.     <local:Converter x:Key="Converter"/>    {   
  263.     <local:Converter x:Key="Converter"/>   
  264.     <local:Converter x:Key="Converter"/>   
  265.     <local:Converter x:Key="Converter"/>var path1 = new DrawingPath   
  266.     <local:Converter x:Key="Converter"/>   
  267.     <local:Converter x:Key="Converter"/>   
  268.     <local:Converter x:Key="Converter"/>{   
  269.     <local:Converter x:Key="Converter"/>   
  270.     <local:Converter x:Key="Converter"/>   
  271.     <local:Converter x:Key="Converter"/>    StrokeColor = StrokeColor,   
  272.     <local:Converter x:Key="Converter"/>   
  273.     <local:Converter x:Key="Converter"/>   
  274.     <local:Converter x:Key="Converter"/>    StrokeThickness = StrokeThickness,   
  275.     <local:Converter x:Key="Converter"/>   
  276.     <local:Converter x:Key="Converter"/>   
  277.     <local:Converter x:Key="Converter"/>    Pos = Pos   
  278.     <local:Converter x:Key="Converter"/>   
  279.     <local:Converter x:Key="Converter"/>   
  280.     <local:Converter x:Key="Converter"/>};   
  281.     <local:Converter x:Key="Converter"/>   
  282.     <local:Converter x:Key="Converter"/>   
  283.     <local:Converter x:Key="Converter"/>path1.Path.MoveTo(points.ElementAt(0));   
  284.     <local:Converter x:Key="Converter"/>   
  285.     <local:Converter x:Key="Converter"/>   
  286.     <local:Converter x:Key="Converter"/>for (int i = 1; i < bestIndex; i++)   
  287.     <local:Converter x:Key="Converter"/>   
  288.     <local:Converter x:Key="Converter"/>   
  289.     <local:Converter x:Key="Converter"/>{   
  290.     <local:Converter x:Key="Converter"/>   
  291.     <local:Converter x:Key="Converter"/>   
  292.     <local:Converter x:Key="Converter"/>    path1.Path.LineTo(points.ElementAt(i));   
  293.     <local:Converter x:Key="Converter"/>   
  294.     <local:Converter x:Key="Converter"/>   
  295.     <local:Converter x:Key="Converter"/>}   
  296.     <local:Converter x:Key="Converter"/>   
  297.     <local:Converter x:Key="Converter"/>   
  298.     <local:Converter x:Key="Converter"/>newPaths.Add(path1);   
  299.     <local:Converter x:Key="Converter"/>   
  300.     <local:Converter x:Key="Converter"/>    }   
  301.     <local:Converter x:Key="Converter"/>   
  302.     <local:Converter x:Key="Converter"/>    // 第二段路径   
  303.     <local:Converter x:Key="Converter"/>   
  304.     <local:Converter x:Key="Converter"/>    if (bestIndex < points.Count() - 1)   
  305.     <local:Converter x:Key="Converter"/>   
  306.     <local:Converter x:Key="Converter"/>    {   
  307.     <local:Converter x:Key="Converter"/>   
  308.     <local:Converter x:Key="Converter"/>   
  309.     <local:Converter x:Key="Converter"/>var path2 = new DrawingPath   
  310.     <local:Converter x:Key="Converter"/>   
  311.     <local:Converter x:Key="Converter"/>   
  312.     <local:Converter x:Key="Converter"/>{   
  313.     <local:Converter x:Key="Converter"/>   
  314.     <local:Converter x:Key="Converter"/>   
  315.     <local:Converter x:Key="Converter"/>    StrokeColor = StrokeColor,   
  316.     <local:Converter x:Key="Converter"/>   
  317.     <local:Converter x:Key="Converter"/>   
  318.     <local:Converter x:Key="Converter"/>    StrokeThickness = StrokeThickness,   
  319.     <local:Converter x:Key="Converter"/>   
  320.     <local:Converter x:Key="Converter"/>   
  321.     <local:Converter x:Key="Converter"/>    Pos = Pos   
  322.     <local:Converter x:Key="Converter"/>   
  323.     <local:Converter x:Key="Converter"/>   
  324.     <local:Converter x:Key="Converter"/>};   
  325.     <local:Converter x:Key="Converter"/>   
  326.     <local:Converter x:Key="Converter"/>   
  327.     <local:Converter x:Key="Converter"/>path2.Path.MoveTo(points.ElementAt(bestIndex));   
  328.     <local:Converter x:Key="Converter"/>   
  329.     <local:Converter x:Key="Converter"/>   
  330.     <local:Converter x:Key="Converter"/>for (int i = bestIndex + 1; i < points.Count(); i++)   
  331.     <local:Converter x:Key="Converter"/>   
  332.     <local:Converter x:Key="Converter"/>   
  333.     <local:Converter x:Key="Converter"/>{   
  334.     <local:Converter x:Key="Converter"/>   
  335.     <local:Converter x:Key="Converter"/>   
  336.     <local:Converter x:Key="Converter"/>    path2.Path.LineTo(points.ElementAt(i));   
  337.     <local:Converter x:Key="Converter"/>   
  338.     <local:Converter x:Key="Converter"/>   
  339.     <local:Converter x:Key="Converter"/>}   
  340.     <local:Converter x:Key="Converter"/>   
  341.     <local:Converter x:Key="Converter"/>   
  342.     <local:Converter x:Key="Converter"/>newPaths.Add(path2);   
  343.     <local:Converter x:Key="Converter"/>   
  344.     <local:Converter x:Key="Converter"/>    }   
  345.     <local:Converter x:Key="Converter"/>   
  346.     <local:Converter x:Key="Converter"/>}   
  347.     <local:Converter x:Key="Converter"/>   
  348.     <local:Converter x:Key="Converter"/>return newPaths;   
  349.     <local:Converter x:Key="Converter"/>    }   
  350.     <local:Converter x:Key="Converter"/>}   
  351.     <local:Converter x:Key="Converter"/>public enum InkCanvasEditingMode { Ink, Select, Erase }   
  352.     <local:Converter x:Key="Converter"/>public static readonly BindableProperty EditingModeProperty =   
  353.     <local:Converter x:Key="Converter"/>    BindableProperty.Create(nameof(EditingMode), typeof(InkCanvasEditingMode), typeof(InkCanvas),   
  354.     <local:Converter x:Key="Converter"/>   
  355.     <local:Converter x:Key="Converter"/>InkCanvasEditingMode.Ink, BindingMode.TwoWay, propertyChanged: OnEditingModeChanged);   
  356.     <local:Converter x:Key="Converter"/>private static void OnEditingModeChanged(BindableObject bindable, object oldValue, object newValue)   
  357.     <local:Converter x:Key="Converter"/>{   
  358.     <local:Converter x:Key="Converter"/>    if (bindable is InkCanvas canvas)   
  359.     <local:Converter x:Key="Converter"/>    {   
  360.     <local:Converter x:Key="Converter"/>   
  361.     <local:Converter x:Key="Converter"/>canvas.ClearSelection();   
  362.     <local:Converter x:Key="Converter"/>   
  363.     <local:Converter x:Key="Converter"/>canvas.Invalidate();   
  364.     <local:Converter x:Key="Converter"/>    }   
  365.     <local:Converter x:Key="Converter"/>}   
  366.     <local:Converter x:Key="Converter"/>public InkCanvasEditingMode EditingMode   
  367.     <local:Converter x:Key="Converter"/>{   
  368.     <local:Converter x:Key="Converter"/>    get => (InkCanvasEditingMode)GetValue(EditingModeProperty);   
  369.     <local:Converter x:Key="Converter"/>    set => SetValue(EditingModeProperty, value);   
  370.     <local:Converter x:Key="Converter"/>}   
  371.     <local:Converter x:Key="Converter"/>public ObservableCollection Paths { get; set; } = new ObservableCollection();   
  372.     <local:Converter x:Key="Converter"/>public DrawingPath Eraser { get; }   
  373.     <local:Converter x:Key="Converter"/>public float EraserRadius { get; set; } = 15f; // 增大橡皮擦半径   
  374.     <local:Converter x:Key="Converter"/>private DrawingPath? currentPath;   
  375.     <local:Converter x:Key="Converter"/>private RectF? selectionRect;   
  376.     <local:Converter x:Key="Converter"/>private PointF lastTouchPoint;   
  377.     <local:Converter x:Key="Converter"/>private bool isMovingSelection;   
  378.     <local:Converter x:Key="Converter"/>// 橡皮擦轨迹跟踪   
  379.     <local:Converter x:Key="Converter"/>private readonly List eraserTrail = new List();   
  380.     <local:Converter x:Key="Converter"/>private const int MaxEraserTrailPoints = 5;   
  381.     <local:Converter x:Key="Converter"/>public Color StrokeColor { get; set; } = Colors.Black;   
  382.     <local:Converter x:Key="Converter"/>public Color SelectionColor { get; set; } = Colors.Red;   
  383.     <local:Converter x:Key="Converter"/>public float SelectionStrokeThickness { get; set; } = 1f;   
  384.     <local:Converter x:Key="Converter"/>public float StrokeThickness { get; set; } = 1f;   
  385.     <local:Converter x:Key="Converter"/>public InkCanvas()   
  386.     <local:Converter x:Key="Converter"/>{   
  387.     <local:Converter x:Key="Converter"/>    Drawable = this;   
  388.     <local:Converter x:Key="Converter"/>    BackgroundColor = Colors.Transparent;   
  389.     <local:Converter x:Key="Converter"/>    Eraser = CreateEraserPath();   
  390.     <local:Converter x:Key="Converter"/>    StartInteraction += OnTouchStarted;   
  391.     <local:Converter x:Key="Converter"/>    DragInteraction += OnTouchMoved;   
  392.     <local:Converter x:Key="Converter"/>    EndInteraction += OnTouchEnded;   
  393.     <local:Converter x:Key="Converter"/>}   
  394.     <local:Converter x:Key="Converter"/>private DrawingPath CreateEraserPath()   
  395.     <local:Converter x:Key="Converter"/>{   
  396.     <local:Converter x:Key="Converter"/>    var path = new PathF();   
  397.     <local:Converter x:Key="Converter"/>    var points = new[]   
  398.     <local:Converter x:Key="Converter"/>    {   
  399.     <local:Converter x:Key="Converter"/>    new PointF(107.4f, 13), new PointF(113.7f, 28.8f),   
  400.     <local:Converter x:Key="Converter"/>    new PointF(127.9f, 31.3f), new PointF(117.6f, 43.5f),   
  401.     <local:Converter x:Key="Converter"/>    new PointF(120.1f, 60.8f), new PointF(107.4f, 52.6f),   
  402.     <local:Converter x:Key="Converter"/>    new PointF(94.6f, 60.8f), new PointF(97.1f, 43.5f),   
  403.     <local:Converter x:Key="Converter"/>    new PointF(86.8f, 31.3f), new PointF(101f, 28.8f)   
  404.     <local:Converter x:Key="Converter"/>};   
  405.     <local:Converter x:Key="Converter"/>    path.MoveTo(points[0]);   
  406.     <local:Converter x:Key="Converter"/>    for (int i = 1; i < points.Length; i++)   
  407.     <local:Converter x:Key="Converter"/>    {   
  408.     <local:Converter x:Key="Converter"/>   
  409.     <local:Converter x:Key="Converter"/>path.LineTo(points[i]);   
  410.     <local:Converter x:Key="Converter"/>    }   
  411.     <local:Converter x:Key="Converter"/>    path.Close();   
  412.     <local:Converter x:Key="Converter"/>    return new DrawingPath { Path = path, StrokeColor = Colors.Black, StrokeThickness = 1f };   
  413.     <local:Converter x:Key="Converter"/>}   
  414.     <local:Converter x:Key="Converter"/>private void OnTouchStarted(object? sender, TouchEventArgs e)   
  415.     <local:Converter x:Key="Converter"/>{   
  416.     <local:Converter x:Key="Converter"/>    if (e.Touches.Length == 0) return;   
  417.     <local:Converter x:Key="Converter"/>    var point = e.Touches[0];   
  418.     <local:Converter x:Key="Converter"/>    lastTouchPoint = new PointF(point.X, point.Y);   
  419.     <local:Converter x:Key="Converter"/>    eraserTrail.Clear(); // 清除历史轨迹   
  420.     <local:Converter x:Key="Converter"/>    switch (EditingMode)   
  421.     <local:Converter x:Key="Converter"/>    {   
  422.     <local:Converter x:Key="Converter"/>   
  423.     <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Ink:   
  424.     <local:Converter x:Key="Converter"/>   
  425.     <local:Converter x:Key="Converter"/>    StartInking(lastTouchPoint);   
  426.     <local:Converter x:Key="Converter"/>   
  427.     <local:Converter x:Key="Converter"/>    break;   
  428.     <local:Converter x:Key="Converter"/>   
  429.     <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Select:   
  430.     <local:Converter x:Key="Converter"/>   
  431.     <local:Converter x:Key="Converter"/>    StartSelection(lastTouchPoint);   
  432.     <local:Converter x:Key="Converter"/>   
  433.     <local:Converter x:Key="Converter"/>    break;   
  434.     <local:Converter x:Key="Converter"/>   
  435.     <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Erase:   
  436.     <local:Converter x:Key="Converter"/>   
  437.     <local:Converter x:Key="Converter"/>    StartErase(lastTouchPoint);   
  438.     <local:Converter x:Key="Converter"/>   
  439.     <local:Converter x:Key="Converter"/>    eraserTrail.Add(lastTouchPoint); // 添加起始点   
  440.     <local:Converter x:Key="Converter"/>   
  441.     <local:Converter x:Key="Converter"/>    break;   
  442.     <local:Converter x:Key="Converter"/>    }   
  443.     <local:Converter x:Key="Converter"/>    Invalidate();   
  444.     <local:Converter x:Key="Converter"/>}   
  445.     <local:Converter x:Key="Converter"/>private void StartInking(PointF startPoint)   
  446.     <local:Converter x:Key="Converter"/>{   
  447.     <local:Converter x:Key="Converter"/>    currentPath = new DrawingPath   
  448.     <local:Converter x:Key="Converter"/>    {   
  449.     <local:Converter x:Key="Converter"/>   
  450.     <local:Converter x:Key="Converter"/>StrokeColor = StrokeColor,   
  451.     <local:Converter x:Key="Converter"/>   
  452.     <local:Converter x:Key="Converter"/>StrokeThickness = StrokeThickness,   
  453.     <local:Converter x:Key="Converter"/>   
  454.     <local:Converter x:Key="Converter"/>Pos = PointF.Zero   
  455.     <local:Converter x:Key="Converter"/>    };   
  456.     <local:Converter x:Key="Converter"/>    currentPath.Path.MoveTo(startPoint.X, startPoint.Y);   
  457.     <local:Converter x:Key="Converter"/>    Paths.Add(currentPath);   
  458.     <local:Converter x:Key="Converter"/>}   
  459.     <local:Converter x:Key="Converter"/>private void StartSelection(PointF startPoint)   
  460.     <local:Converter x:Key="Converter"/>{   
  461.     <local:Converter x:Key="Converter"/>    isMovingSelection = Paths.Any(p => p.IsSelected && p.Bounds.Contains(startPoint));   
  462.     <local:Converter x:Key="Converter"/>    if (!isMovingSelection)   
  463.     <local:Converter x:Key="Converter"/>    {   
  464.     <local:Converter x:Key="Converter"/>   
  465.     <local:Converter x:Key="Converter"/>ClearSelection();   
  466.     <local:Converter x:Key="Converter"/>   
  467.     <local:Converter x:Key="Converter"/>var clickedPath = Paths.LastOrDefault(p => p.Bounds.Contains(startPoint));   
  468.     <local:Converter x:Key="Converter"/>   
  469.     <local:Converter x:Key="Converter"/>if (clickedPath != null)   
  470.     <local:Converter x:Key="Converter"/>   
  471.     <local:Converter x:Key="Converter"/>{   
  472.     <local:Converter x:Key="Converter"/>   
  473.     <local:Converter x:Key="Converter"/>    clickedPath.IsSelected = true;   
  474.     <local:Converter x:Key="Converter"/>   
  475.     <local:Converter x:Key="Converter"/>    isMovingSelection = true;   
  476.     <local:Converter x:Key="Converter"/>   
  477.     <local:Converter x:Key="Converter"/>}   
  478.     <local:Converter x:Key="Converter"/>   
  479.     <local:Converter x:Key="Converter"/>else   
  480.     <local:Converter x:Key="Converter"/>   
  481.     <local:Converter x:Key="Converter"/>{   
  482.     <local:Converter x:Key="Converter"/>   
  483.     <local:Converter x:Key="Converter"/>    selectionRect = new RectF(startPoint, SizeF.Zero);   
  484.     <local:Converter x:Key="Converter"/>   
  485.     <local:Converter x:Key="Converter"/>}   
  486.     <local:Converter x:Key="Converter"/>    }   
  487.     <local:Converter x:Key="Converter"/>}   
  488.     <local:Converter x:Key="Converter"/>private void StartErase(PointF startPoint)   
  489.     <local:Converter x:Key="Converter"/>{   
  490.     <local:Converter x:Key="Converter"/>    Eraser.Pos = new PointF(startPoint.X - Eraser.Path.Bounds.Width / 2,   
  491.     <local:Converter x:Key="Converter"/>   
  492.     <local:Converter x:Key="Converter"/>   
  493.     <local:Converter x:Key="Converter"/>   
  494.     <local:Converter x:Key="Converter"/>    startPoint.Y - Eraser.Path.Bounds.Height / 4);   
  495.     <local:Converter x:Key="Converter"/>    Eraser.IsSelected = true;   
  496.     <local:Converter x:Key="Converter"/>}   
  497.     <local:Converter x:Key="Converter"/>private void OnTouchMoved(object? sender, TouchEventArgs e)   
  498.     <local:Converter x:Key="Converter"/>{   
  499.     <local:Converter x:Key="Converter"/>    if (e.Touches.Length == 0) return;   
  500.     <local:Converter x:Key="Converter"/>    var currentPoint = new PointF(e.Touches[0].X, e.Touches[0].Y);   
  501.     <local:Converter x:Key="Converter"/>    switch (EditingMode)   
  502.     <local:Converter x:Key="Converter"/>    {   
  503.     <local:Converter x:Key="Converter"/>   
  504.     <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Ink:   
  505.     <local:Converter x:Key="Converter"/>   
  506.     <local:Converter x:Key="Converter"/>    ContinueInking(currentPoint);   
  507.     <local:Converter x:Key="Converter"/>   
  508.     <local:Converter x:Key="Converter"/>    break;   
  509.     <local:Converter x:Key="Converter"/>   
  510.     <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Select:   
  511.     <local:Converter x:Key="Converter"/>   
  512.     <local:Converter x:Key="Converter"/>    UpdateSelection(currentPoint);   
  513.     <local:Converter x:Key="Converter"/>   
  514.     <local:Converter x:Key="Converter"/>    break;   
  515.     <local:Converter x:Key="Converter"/>   
  516.     <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Erase:   
  517.     <local:Converter x:Key="Converter"/>   
  518.     <local:Converter x:Key="Converter"/>    UpdateEraser(currentPoint);   
  519.     <local:Converter x:Key="Converter"/>   
  520.     <local:Converter x:Key="Converter"/>    ErasePaths();   
  521.     <local:Converter x:Key="Converter"/>   
  522.     <local:Converter x:Key="Converter"/>    break;   
  523.     <local:Converter x:Key="Converter"/>    }   
  524.     <local:Converter x:Key="Converter"/>    Invalidate();   
  525.     <local:Converter x:Key="Converter"/>}   
  526.     <local:Converter x:Key="Converter"/>private void ContinueInking(PointF currentPoint)   
  527.     <local:Converter x:Key="Converter"/>{   
  528.     <local:Converter x:Key="Converter"/>    if (currentPath == null) return;   
  529.     <local:Converter x:Key="Converter"/>    const float minDistance = 1.0f;   
  530.     <local:Converter x:Key="Converter"/>    float dx = currentPoint.X - lastTouchPoint.X;   
  531.     <local:Converter x:Key="Converter"/>    float dy = currentPoint.Y - lastTouchPoint.Y;   
  532.     <local:Converter x:Key="Converter"/>    if (dx * dx + dy * dy > minDistance * minDistance)   
  533.     <local:Converter x:Key="Converter"/>    {   
  534.     <local:Converter x:Key="Converter"/>   
  535.     <local:Converter x:Key="Converter"/>currentPath.LineTo(currentPoint.X, currentPoint.Y);   
  536.     <local:Converter x:Key="Converter"/>   
  537.     <local:Converter x:Key="Converter"/>lastTouchPoint = currentPoint;   
  538.     <local:Converter x:Key="Converter"/>    }   
  539.     <local:Converter x:Key="Converter"/>}   
  540.     <local:Converter x:Key="Converter"/>private void UpdateSelection(PointF currentPoint)   
  541.     <local:Converter x:Key="Converter"/>{   
  542.     <local:Converter x:Key="Converter"/>    if (isMovingSelection)   
  543.     <local:Converter x:Key="Converter"/>    {   
  544.     <local:Converter x:Key="Converter"/>   
  545.     <local:Converter x:Key="Converter"/>MoveSelectedPaths(currentPoint);   
  546.     <local:Converter x:Key="Converter"/>    }   
  547.     <local:Converter x:Key="Converter"/>    else if (selectionRect.HasValue)   
  548.     <local:Converter x:Key="Converter"/>    {   
  549.     <local:Converter x:Key="Converter"/>   
  550.     <local:Converter x:Key="Converter"/>UpdateSelectionRect(currentPoint);   
  551.     <local:Converter x:Key="Converter"/>    }   
  552.     <local:Converter x:Key="Converter"/>}   
  553.     <local:Converter x:Key="Converter"/>private void UpdateEraser(PointF currentPoint)   
  554.     <local:Converter x:Key="Converter"/>{   
  555.     <local:Converter x:Key="Converter"/>    Eraser.Pos = new PointF(currentPoint.X - Eraser.Path.Bounds.Width / 2,   
  556.     <local:Converter x:Key="Converter"/>   
  557.     <local:Converter x:Key="Converter"/>   
  558.     <local:Converter x:Key="Converter"/>   
  559.     <local:Converter x:Key="Converter"/>    currentPoint.Y - Eraser.Path.Bounds.Height / 4);   
  560.     <local:Converter x:Key="Converter"/>    // 添加到橡皮擦轨迹   
  561.     <local:Converter x:Key="Converter"/>    eraserTrail.Add(Eraser.Pos);   
  562.     <local:Converter x:Key="Converter"/>    if (eraserTrail.Count > MaxEraserTrailPoints)   
  563.     <local:Converter x:Key="Converter"/>    {   
  564.     <local:Converter x:Key="Converter"/>   
  565.     <local:Converter x:Key="Converter"/>eraserTrail.RemoveAt(0);   
  566.     <local:Converter x:Key="Converter"/>    }   
  567.     <local:Converter x:Key="Converter"/>    lastTouchPoint = currentPoint;   
  568.     <local:Converter x:Key="Converter"/>}   
  569.     <local:Converter x:Key="Converter"/>// 优化擦除逻辑   
  570.     <local:Converter x:Key="Converter"/>private void ErasePaths()   
  571.     <local:Converter x:Key="Converter"/>{   
  572.     <local:Converter x:Key="Converter"/>    // 倒序遍历所有路径   
  573.     <local:Converter x:Key="Converter"/>    for (int i = Paths.Count - 1; i >= 0; i--)   
  574.     <local:Converter x:Key="Converter"/>    {   
  575.     <local:Converter x:Key="Converter"/>   
  576.     <local:Converter x:Key="Converter"/>var path = Paths[i];   
  577.     <local:Converter x:Key="Converter"/>   
  578.     <local:Converter x:Key="Converter"/>// 检查橡皮擦轨迹上的所有点   
  579.     <local:Converter x:Key="Converter"/>   
  580.     <local:Converter x:Key="Converter"/>foreach (var trailPoint in eraserTrail)   
  581.     <local:Converter x:Key="Converter"/>   
  582.     <local:Converter x:Key="Converter"/>{   
  583.     <local:Converter x:Key="Converter"/>   
  584.     <local:Converter x:Key="Converter"/>    if (path.IntersectAt(trailPoint, EraserRadius))   
  585.     <local:Converter x:Key="Converter"/>   
  586.     <local:Converter x:Key="Converter"/>    {   
  587.     <local:Converter x:Key="Converter"/>   
  588.     <local:Converter x:Key="Converter"/>   
  589.     <local:Converter x:Key="Converter"/>var newPaths = path.SplitAt(trailPoint, EraserRadius);   
  590.     <local:Converter x:Key="Converter"/>   
  591.     <local:Converter x:Key="Converter"/>   
  592.     <local:Converter x:Key="Converter"/>if (newPaths.Count > 0)   
  593.     <local:Converter x:Key="Converter"/>   
  594.     <local:Converter x:Key="Converter"/>   
  595.     <local:Converter x:Key="Converter"/>{   
  596.     <local:Converter x:Key="Converter"/>   
  597.     <local:Converter x:Key="Converter"/>   
  598.     <local:Converter x:Key="Converter"/>    Paths.RemoveAt(i);   
  599.     <local:Converter x:Key="Converter"/>   
  600.     <local:Converter x:Key="Converter"/>   
  601.     <local:Converter x:Key="Converter"/>    foreach (var newPath in newPaths)   
  602.     <local:Converter x:Key="Converter"/>   
  603.     <local:Converter x:Key="Converter"/>   
  604.     <local:Converter x:Key="Converter"/>    {   
  605.     <local:Converter x:Key="Converter"/>   
  606.     <local:Converter x:Key="Converter"/>   
  607.     <local:Converter x:Key="Converter"/>   
  608.     <local:Converter x:Key="Converter"/>if (newPath.Path.Count >= 2) // 只添加有效路径   
  609.     <local:Converter x:Key="Converter"/>   
  610.     <local:Converter x:Key="Converter"/>   
  611.     <local:Converter x:Key="Converter"/>   
  612.     <local:Converter x:Key="Converter"/>{   
  613.     <local:Converter x:Key="Converter"/>   
  614.     <local:Converter x:Key="Converter"/>   
  615.     <local:Converter x:Key="Converter"/>   
  616.     <local:Converter x:Key="Converter"/>    Paths.Add(newPath);   
  617.     <local:Converter x:Key="Converter"/>   
  618.     <local:Converter x:Key="Converter"/>   
  619.     <local:Converter x:Key="Converter"/>   
  620.     <local:Converter x:Key="Converter"/>}   
  621.     <local:Converter x:Key="Converter"/>   
  622.     <local:Converter x:Key="Converter"/>   
  623.     <local:Converter x:Key="Converter"/>    }   
  624.     <local:Converter x:Key="Converter"/>   
  625.     <local:Converter x:Key="Converter"/>   
  626.     <local:Converter x:Key="Converter"/>    break; // 路径已被处理,跳出循环   
  627.     <local:Converter x:Key="Converter"/>   
  628.     <local:Converter x:Key="Converter"/>   
  629.     <local:Converter x:Key="Converter"/>}   
  630.     <local:Converter x:Key="Converter"/>   
  631.     <local:Converter x:Key="Converter"/>   
  632.     <local:Converter x:Key="Converter"/>else   
  633.     <local:Converter x:Key="Converter"/>   
  634.     <local:Converter x:Key="Converter"/>   
  635.     <local:Converter x:Key="Converter"/>{   
  636.     <local:Converter x:Key="Converter"/>   
  637.     <local:Converter x:Key="Converter"/>   
  638.     <local:Converter x:Key="Converter"/>    // 没有新路径表示整个路径应被删除   
  639.     <local:Converter x:Key="Converter"/>   
  640.     <local:Converter x:Key="Converter"/>   
  641.     <local:Converter x:Key="Converter"/>    Paths.RemoveAt(i);   
  642.     <local:Converter x:Key="Converter"/>   
  643.     <local:Converter x:Key="Converter"/>   
  644.     <local:Converter x:Key="Converter"/>    break;   
  645.     <local:Converter x:Key="Converter"/>   
  646.     <local:Converter x:Key="Converter"/>   
  647.     <local:Converter x:Key="Converter"/>}   
  648.     <local:Converter x:Key="Converter"/>   
  649.     <local:Converter x:Key="Converter"/>    }   
  650.     <local:Converter x:Key="Converter"/>   
  651.     <local:Converter x:Key="Converter"/>}   
  652.     <local:Converter x:Key="Converter"/>    }   
  653.     <local:Converter x:Key="Converter"/>}   
  654.     <local:Converter x:Key="Converter"/>private void MoveSelectedPaths(PointF currentPoint)   
  655.     <local:Converter x:Key="Converter"/>{   
  656.     <local:Converter x:Key="Converter"/>    float deltaX = currentPoint.X - lastTouchPoint.X;   
  657.     <local:Converter x:Key="Converter"/>    float deltaY = currentPoint.Y - lastTouchPoint.Y;   
  658.     <local:Converter x:Key="Converter"/>    foreach (var path in Paths)   
  659.     <local:Converter x:Key="Converter"/>    {   
  660.     <local:Converter x:Key="Converter"/>   
  661.     <local:Converter x:Key="Converter"/>if (path.IsSelected)   
  662.     <local:Converter x:Key="Converter"/>   
  663.     <local:Converter x:Key="Converter"/>{   
  664.     <local:Converter x:Key="Converter"/>   
  665.     <local:Converter x:Key="Converter"/>    path.Pos = new PointF(path.Pos.X + deltaX, path.Pos.Y + deltaY);   
  666.     <local:Converter x:Key="Converter"/>   
  667.     <local:Converter x:Key="Converter"/>    path.InvalidateBounds();   
  668.     <local:Converter x:Key="Converter"/>   
  669.     <local:Converter x:Key="Converter"/>}   
  670.     <local:Converter x:Key="Converter"/>    }   
  671.     <local:Converter x:Key="Converter"/>    lastTouchPoint = currentPoint;   
  672.     <local:Converter x:Key="Converter"/>}   
  673.     <local:Converter x:Key="Converter"/>private void UpdateSelectionRect(PointF currentPoint)   
  674.     <local:Converter x:Key="Converter"/>{   
  675.     <local:Converter x:Key="Converter"/>    float x = Math.Min(lastTouchPoint.X, currentPoint.X);   
  676.     <local:Converter x:Key="Converter"/>    float y = Math.Min(lastTouchPoint.Y, currentPoint.Y);   
  677.     <local:Converter x:Key="Converter"/>    float width = Math.Abs(currentPoint.X - lastTouchPoint.X);   
  678.     <local:Converter x:Key="Converter"/>    float height = Math.Abs(currentPoint.Y - lastTouchPoint.Y);   
  679.     <local:Converter x:Key="Converter"/>    selectionRect = new RectF(x, y, width, height);   
  680.     <local:Converter x:Key="Converter"/>}   
  681.     <local:Converter x:Key="Converter"/>private void OnTouchEnded(object? sender, TouchEventArgs e)   
  682.     <local:Converter x:Key="Converter"/>{   
  683.     <local:Converter x:Key="Converter"/>    switch (EditingMode)   
  684.     <local:Converter x:Key="Converter"/>    {   
  685.     <local:Converter x:Key="Converter"/>   
  686.     <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Select when selectionRect.HasValue:   
  687.     <local:Converter x:Key="Converter"/>   
  688.     <local:Converter x:Key="Converter"/>    FinalizeSelection();   
  689.     <local:Converter x:Key="Converter"/>   
  690.     <local:Converter x:Key="Converter"/>    break;   
  691.     <local:Converter x:Key="Converter"/>    }   
  692.     <local:Converter x:Key="Converter"/>    currentPath = null;   
  693.     <local:Converter x:Key="Converter"/>    selectionRect = null;   
  694.     <local:Converter x:Key="Converter"/>    isMovingSelection = false;   
  695.     <local:Converter x:Key="Converter"/>    Eraser.IsSelected = false;   
  696.     <local:Converter x:Key="Converter"/>    eraserTrail.Clear(); // 清除橡皮擦轨迹   
  697.     <local:Converter x:Key="Converter"/>    Invalidate();   
  698.     <local:Converter x:Key="Converter"/>}   
  699.     <local:Converter x:Key="Converter"/>private void FinalizeSelection()   
  700.     <local:Converter x:Key="Converter"/>{   
  701.     <local:Converter x:Key="Converter"/>    var selection = selectionRect!.Value;   
  702.     <local:Converter x:Key="Converter"/>    foreach (var path in Paths)   
  703.     <local:Converter x:Key="Converter"/>    {   
  704.     <local:Converter x:Key="Converter"/>   
  705.     <local:Converter x:Key="Converter"/>if (!selection.IntersectsWith(path.Bounds)) continue;   
  706.     <local:Converter x:Key="Converter"/>   
  707.     <local:Converter x:Key="Converter"/>if (selection.Contains(path.Bounds))   
  708.     <local:Converter x:Key="Converter"/>   
  709.     <local:Converter x:Key="Converter"/>{   
  710.     <local:Converter x:Key="Converter"/>   
  711.     <local:Converter x:Key="Converter"/>    path.IsSelected = true;   
  712.     <local:Converter x:Key="Converter"/>   
  713.     <local:Converter x:Key="Converter"/>    continue;   
  714.     <local:Converter x:Key="Converter"/>   
  715.     <local:Converter x:Key="Converter"/>}   
  716.     <local:Converter x:Key="Converter"/>   
  717.     <local:Converter x:Key="Converter"/>foreach (var point in path.Path.Points)   
  718.     <local:Converter x:Key="Converter"/>   
  719.     <local:Converter x:Key="Converter"/>{   
  720.     <local:Converter x:Key="Converter"/>   
  721.     <local:Converter x:Key="Converter"/>    var absolutePoint = new PointF(point.X + path.Pos.X, point.Y + path.Pos.Y);   
  722.     <local:Converter x:Key="Converter"/>   
  723.     <local:Converter x:Key="Converter"/>    if (selection.Contains(absolutePoint))   
  724.     <local:Converter x:Key="Converter"/>   
  725.     <local:Converter x:Key="Converter"/>    {   
  726.     <local:Converter x:Key="Converter"/>   
  727.     <local:Converter x:Key="Converter"/>   
  728.     <local:Converter x:Key="Converter"/>path.IsSelected = true;   
  729.     <local:Converter x:Key="Converter"/>   
  730.     <local:Converter x:Key="Converter"/>   
  731.     <local:Converter x:Key="Converter"/>break;   
  732.     <local:Converter x:Key="Converter"/>   
  733.     <local:Converter x:Key="Converter"/>    }   
  734.     <local:Converter x:Key="Converter"/>   
  735.     <local:Converter x:Key="Converter"/>}   
  736.     <local:Converter x:Key="Converter"/>    }   
  737.     <local:Converter x:Key="Converter"/>}   
  738.     <local:Converter x:Key="Converter"/>public void ClearSelection()   
  739.     <local:Converter x:Key="Converter"/>{   
  740.     <local:Converter x:Key="Converter"/>    foreach (var path in Paths)   
  741.     <local:Converter x:Key="Converter"/>    {   
  742.     <local:Converter x:Key="Converter"/>   
  743.     <local:Converter x:Key="Converter"/>path.IsSelected = false;   
  744.     <local:Converter x:Key="Converter"/>    }   
  745.     <local:Converter x:Key="Converter"/>}   
  746.     <local:Converter x:Key="Converter"/>public void Draw(ICanvas canvas, RectF dirtyRect)   
  747.     <local:Converter x:Key="Converter"/>{   
  748.     <local:Converter x:Key="Converter"/>    canvas.FillColor = BackgroundColor;   
  749.     <local:Converter x:Key="Converter"/>    canvas.FillRectangle(dirtyRect);   
  750.     <local:Converter x:Key="Converter"/>    canvas.StrokeLineCap = LineCap.Round;   
  751.     <local:Converter x:Key="Converter"/>    canvas.StrokeLineJoin = LineJoin.Round;   
  752.     <local:Converter x:Key="Converter"/>    // 绘制所有路径   
  753.     <local:Converter x:Key="Converter"/>    foreach (var path in Paths)   
  754.     <local:Converter x:Key="Converter"/>    {   
  755.     <local:Converter x:Key="Converter"/>   
  756.     <local:Converter x:Key="Converter"/>DrawPath(canvas, path);   
  757.     <local:Converter x:Key="Converter"/>    }   
  758.     <local:Converter x:Key="Converter"/>    // 绘制橡皮擦(如果被选中)   
  759.     <local:Converter x:Key="Converter"/>    if (Eraser.IsSelected)   
  760.     <local:Converter x:Key="Converter"/>    {   
  761.     <local:Converter x:Key="Converter"/>   
  762.     <local:Converter x:Key="Converter"/>DrawEraser(canvas);   
  763.     <local:Converter x:Key="Converter"/>    }   
  764.     <local:Converter x:Key="Converter"/>    // 绘制选择框   
  765.     <local:Converter x:Key="Converter"/>    if (selectionRect.HasValue)   
  766.     <local:Converter x:Key="Converter"/>    {   
  767.     <local:Converter x:Key="Converter"/>   
  768.     <local:Converter x:Key="Converter"/>DrawSelectionRect(canvas, selectionRect.Value);   
  769.     <local:Converter x:Key="Converter"/>    }   
  770.     <local:Converter x:Key="Converter"/>}   
  771.     <local:Converter x:Key="Converter"/>private void DrawPath(ICanvas canvas, DrawingPath path)   
  772.     <local:Converter x:Key="Converter"/>{   
  773.     <local:Converter x:Key="Converter"/>    var strokeColor = path.StrokeColor ?? Colors.Black;   
  774.     <local:Converter x:Key="Converter"/>    float strokeSize = path.IsSelected ? path.StrokeThickness * 1.5f : path.StrokeThickness;   
  775.     <local:Converter x:Key="Converter"/>    if (!path.IsSelected)   
  776.     <local:Converter x:Key="Converter"/>    {   
  777.     <local:Converter x:Key="Converter"/>   
  778.     <local:Converter x:Key="Converter"/>strokeColor = strokeColor.WithAlpha(0.5f);   
  779.     <local:Converter x:Key="Converter"/>    }   
  780.     <local:Converter x:Key="Converter"/>    canvas.StrokeColor = strokeColor;   
  781.     <local:Converter x:Key="Converter"/>    canvas.StrokeSize = strokeSize;   
  782.     <local:Converter x:Key="Converter"/>    canvas.SaveState();   
  783.     <local:Converter x:Key="Converter"/>    canvas.Translate(path.Pos.X, path.Pos.Y);   
  784.     <local:Converter x:Key="Converter"/>    canvas.DrawPath(path.Path);   
  785.     <local:Converter x:Key="Converter"/>    canvas.RestoreState();   
  786.     <local:Converter x:Key="Converter"/>}   
  787.     <local:Converter x:Key="Converter"/>private void DrawEraser(ICanvas canvas)   
  788.     <local:Converter x:Key="Converter"/>{   
  789.     <local:Converter x:Key="Converter"/>    canvas.SaveState();   
  790.     <local:Converter x:Key="Converter"/>    canvas.Translate(Eraser.Pos.X, Eraser.Pos.Y);   
  791.     <local:Converter x:Key="Converter"/>    canvas.Scale(0.2f, 0.2f);   
  792.     <local:Converter x:Key="Converter"/>    canvas.StrokeColor = Eraser.StrokeColor ?? Colors.Black;   
  793.     <local:Converter x:Key="Converter"/>    canvas.StrokeSize = Eraser.StrokeThickness;   
  794.     <local:Converter x:Key="Converter"/>    canvas.FillColor = Color.FromArgb("#FFD700");   
  795.     <local:Converter x:Key="Converter"/>    canvas.FillPath(Eraser.Path);   
  796.     <local:Converter x:Key="Converter"/>    canvas.DrawPath(Eraser.Path);   
  797.     <local:Converter x:Key="Converter"/>    canvas.RestoreState();   
  798.     <local:Converter x:Key="Converter"/>}   
  799.     <local:Converter x:Key="Converter"/>private void DrawSelectionRect(ICanvas canvas, RectF rect)   
  800.     <local:Converter x:Key="Converter"/>{   
  801.     <local:Converter x:Key="Converter"/>    canvas.SaveState();   
  802.     <local:Converter x:Key="Converter"/>    canvas.StrokeColor = SelectionColor;   
  803.     <local:Converter x:Key="Converter"/>    canvas.StrokeSize = SelectionStrokeThickness;   
  804.     <local:Converter x:Key="Converter"/>    canvas.StrokeDashPattern = new float[] { 5, 3 };   
  805.     <local:Converter x:Key="Converter"/>    canvas.DrawRectangle(rect);   
  806.     <local:Converter x:Key="Converter"/>    canvas.RestoreState();   
  807.     <local:Converter x:Key="Converter"/>}   
  808.     <local:Converter x:Key="Converter"/>// 静态工具方法   
  809.     <local:Converter x:Key="Converter"/>public static float Distance(PointF a, PointF b)   
  810.     <local:Converter x:Key="Converter"/>    => (float)Math.Sqrt(Math.Pow(a.X - b.X, 2) + Math.Pow(a.Y - b.Y, 2));   
  811.     <local:Converter x:Key="Converter"/>public static float DistanceSquared(PointF a, PointF b)   
  812.     <local:Converter x:Key="Converter"/>    => (a.X - b.X) * (a.X - b.X) + (a.Y - b.Y) * (a.Y - b.Y);   
  813.     <local:Converter x:Key="Converter"/>public static float PointToLineDistance(PointF lineStart, PointF lineEnd, PointF point)   
  814.     <local:Converter x:Key="Converter"/>{   
  815.     <local:Converter x:Key="Converter"/>    float l2 = DistanceSquared(lineStart, lineEnd);   
  816.     <local:Converter x:Key="Converter"/>    if (l2 == 0) return Distance(point, lineStart);   
  817.     <local:Converter x:Key="Converter"/>    float t = Math.Max(0, Math.Min(1, Vector2.Dot(   
  818.     <local:Converter x:Key="Converter"/>   
  819.     <local:Converter x:Key="Converter"/>new Vector2(point.X - lineStart.X, point.Y - lineStart.Y),   
  820.     <local:Converter x:Key="Converter"/>   
  821.     <local:Converter x:Key="Converter"/>new Vector2(lineEnd.X - lineStart.X, lineEnd.Y - lineStart.Y)) / l2));   
  822.     <local:Converter x:Key="Converter"/>    PointF projection = new PointF(   
  823.     <local:Converter x:Key="Converter"/>   
  824.     <local:Converter x:Key="Converter"/>lineStart.X + t * (lineEnd.X - lineStart.X),   
  825.     <local:Converter x:Key="Converter"/>   
  826.     <local:Converter x:Key="Converter"/>lineStart.Y + t * (lineEnd.Y - lineStart.Y)   
  827.     <local:Converter x:Key="Converter"/>    );   
  828.     <local:Converter x:Key="Converter"/>    return Distance(point, projection);   
  829.     <local:Converter x:Key="Converter"/>}    }
复制代码
SimpleInkCanvas.xaml
  1.    
  2.     <local:Converter x:Key="Converter"/>   
  3.     <local:Converter x:Key="Converter"/>   
  4.     <local:Converter x:Key="Converter"/>   
  5.     <local:Converter x:Key="Converter"/>   
  6.     <local:Converter x:Key="Converter"/>   
  7.     <local:Converter x:Key="Converter"/>   
  8.     <local:Converter x:Key="Converter"/>   
  9.     <local:Converter x:Key="Converter"/>
复制代码
对应的cs代码
  1. 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)))                {   
  2.     <local:Converter x:Key="Converter"/>    lstEditingMode.Items.Add(mode.ToString());                        lstEditingMode.SelectedIndex = 0;   
  3.     <local:Converter x:Key="Converter"/>}    }}
复制代码
运行效果
1.png

 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册