找回密码
 立即注册
首页 业界区 业界 C# Avalonia 03 - LayoutPanels - SimpleInkCanvas

C# Avalonia 03 - LayoutPanels - SimpleInkCanvas

坐褐 7 小时前
这次继承C# Avalonia官方自带的Canvas,扩展一个InkCanvas,兼容Canvas的所有功能。为了简化自定义命名控件,建议把自定义控件加入到默认空间。
AssemblyInfo.cs代码如下
  1. using System.Runtime.CompilerServices;
  2. using System.Resources;
  3. using Avalonia.Metadata;
  4. [assembly: NeutralResourcesLanguage("zh-CN")]
  5. [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Shares.Avalonia")]
复制代码
Canvas类有几点需要注意。
1. 自定义内容区域,是通过[Content]属性来描述Controls类。
  1.         [Content]
  2.         public Controls Children { get; } = new Controls();
复制代码
2. Render是sealed,所以不支持重写Render。
  1.         public sealed override void Render(DrawingContext context)
复制代码
现在,我们在Shares.Avalonia共享项目中,创建一个ControlExtensions.cs,实现InkCanvas类。代码如下
  1. using Avalonia;
  2. using Avalonia.Controls;
  3. using Avalonia.Input;
  4. using Avalonia.Media;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. namespace Shares.Avalonia
  9. {
  10.     public enum InkEditingMode
  11.     {
  12.         Ink,
  13.         Erase,
  14.         Select
  15.     }
  16.     public class InkStroke
  17.     {
  18.         public List<Point> Points { get; set; } = new();
  19.         public Color Color { get; set; } = Colors.Black;
  20.         public double Thickness { get; set; } = 1.0;
  21.     }
  22.     public class InkCanvasLayer : Control
  23.     {
  24.         public List<InkStroke> Strokes { get; set; } = new();
  25.         public InkStroke? CurrentStroke { get; set; }
  26.         public List<InkStroke> SelectedStrokes { get; set; } = new();
  27.         public Rect? SelectionRect { get; set; }
  28.         public Rect? SelectionBox { get; set; }
  29.         public override void Render(DrawingContext context)
  30.         {
  31.             base.Render(context);
  32.             foreach (var stroke in Strokes)
  33.             {
  34.                 var isSelected = SelectedStrokes.Contains(stroke);
  35.                 DrawStroke(context, stroke, isSelected);
  36.             }
  37.             if (CurrentStroke != null)
  38.                 DrawStroke(context, CurrentStroke);
  39.             if (SelectionRect.HasValue)
  40.             {
  41.                 context.DrawRectangle(null,
  42.                     new Pen(Brushes.DarkOliveGreen, 1, dashStyle: DashStyle.Dash),
  43.                     SelectionRect.Value);
  44.             }
  45.             if (SelectionBox.HasValue)
  46.             {
  47.                 var pen = new Pen(Brushes.DarkGray, 1, dashStyle: DashStyle.Dash);
  48.                 context.DrawRectangle(null, pen, SelectionBox.Value);
  49.             }
  50.         }
  51.         private void DrawStroke(DrawingContext context, InkStroke stroke, bool isSelected = false)
  52.         {
  53.             if (stroke.Points.Count < 2) return;
  54.             var color = isSelected ? Colors.Black : stroke.Color;
  55.             var thickness = isSelected ? stroke.Thickness * 2 : stroke.Thickness;
  56.             var pen = new Pen(new SolidColorBrush(color), thickness);
  57.             for (int i = 1; i < stroke.Points.Count; i++)
  58.             {
  59.                 context.DrawLine(pen, stroke.Points[i - 1], stroke.Points[i]);
  60.             }
  61.         }
  62.     }
  63.     public class InkCanvas : Canvas
  64.     {
  65.         private readonly InkCanvasLayer layer;
  66.         private List<InkStroke> strokes = new();
  67.         private InkStroke? currentStroke;
  68.         private Stack<List<InkStroke>> undoStack = new();
  69.         private Stack<List<InkStroke>> redoStack = new();
  70.         private bool isSelecting = false;
  71.         private Rect selectionRect;
  72.         private List<InkStroke> selectedStrokes = new();
  73.         private Point selectionStart;
  74.         private bool isDraggingSelection = false;
  75.         private Point lastDragPoint;
  76.         public static readonly StyledProperty<Color> StrokeColorProperty =
  77.             AvaloniaProperty.Register<InkCanvas, Color>(nameof(StrokeColor), Colors.Black);
  78.         public Color StrokeColor
  79.         {
  80.             get => GetValue(StrokeColorProperty);
  81.             set => SetValue(StrokeColorProperty, value);
  82.         }
  83.         public static readonly StyledProperty<double> StrokeThicknessProperty =
  84.             AvaloniaProperty.Register<InkCanvas, double>(nameof(StrokeThickness), 2.0);
  85.         public double StrokeThickness
  86.         {
  87.             get => GetValue(StrokeThicknessProperty);
  88.             set => SetValue(StrokeThicknessProperty, value);
  89.         }
  90.         public static readonly StyledProperty<InkEditingMode> EditingModeProperty =
  91.             AvaloniaProperty.Register<InkCanvas, InkEditingMode>(nameof(EditingMode), InkEditingMode.Ink);
  92.         public InkEditingMode EditingMode
  93.         {
  94.             get => GetValue(EditingModeProperty);
  95.             set => SetValue(EditingModeProperty, value);
  96.         }
  97.         public InkCanvas()
  98.         {
  99.             layer = new InkCanvasLayer();
  100.             Children.Add(layer);
  101.             PointerPressed += OnPointerPressed;
  102.             PointerMoved += OnPointerMoved;
  103.             PointerReleased += OnPointerReleased;
  104.             this.GetObservable(EditingModeProperty).Subscribe(mode =>
  105.             {
  106.                 selectedStrokes.Clear();
  107.                 layer.SelectedStrokes = selectedStrokes;
  108.                 layer.SelectionBox = null;
  109.                 layer.InvalidateVisual();
  110.             });
  111.             Background = Brushes.White;
  112.         }
  113.         protected override Size ArrangeOverride(Size finalSize)
  114.         {
  115.             layer.Arrange(new Rect(finalSize));
  116.             return base.ArrangeOverride(finalSize);
  117.         }
  118.         private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
  119.         {
  120.             var point = e.GetPosition(this);
  121.             if (EditingMode == InkEditingMode.Erase)
  122.             {
  123.                 EraseAtPoint(point);
  124.                 return;
  125.             }
  126.             if (EditingMode == InkEditingMode.Select)
  127.             {
  128.                 selectionStart = point;
  129.                 selectionRect = new Rect(point, point);
  130.                 if (selectedStrokes.Any(s => s.Points.Any(p => Distance(p, point) < 5)))
  131.                 {
  132.                     isDraggingSelection = true;
  133.                     lastDragPoint = point;
  134.                     return;
  135.                 }
  136.                 isSelecting = true;
  137.                 return;
  138.             }
  139.             currentStroke = new InkStroke
  140.             {
  141.                 Color = StrokeColor,
  142.                 Thickness = StrokeThickness
  143.             };
  144.             currentStroke.Points.Add(point);
  145.             layer.CurrentStroke = currentStroke;
  146.             e.Pointer.Capture(this);
  147.         }
  148.         private void OnPointerMoved(object? sender, PointerEventArgs e)
  149.         {
  150.             var point = e.GetPosition(this);
  151.             if (currentStroke != null && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
  152.             {
  153.                 currentStroke.Points.Add(point);
  154.                 layer.InvalidateVisual();
  155.             }
  156.             if (isDraggingSelection && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
  157.             {
  158.                 var delta = point - lastDragPoint;
  159.                 MoveSelected(delta);
  160.                 lastDragPoint = point;
  161.                 UpdateSelectionBox();
  162.             }
  163.             if (isSelecting && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
  164.             {
  165.                 selectionRect = new Rect(selectionStart, point).Normalize();
  166.                 layer.SelectionRect = selectionRect;
  167.                 layer.InvalidateVisual();
  168.             }
  169.         }
  170.         private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
  171.         {
  172.             if (currentStroke != null)
  173.             {
  174.                 SaveUndoState();
  175.                 strokes.Add(currentStroke);
  176.                 currentStroke = null;
  177.                 layer.CurrentStroke = null;
  178.             }
  179.             if (isDraggingSelection)
  180.             {
  181.                 isDraggingSelection = false;
  182.             }
  183.             if (isSelecting)
  184.             {
  185.                 isSelecting = false;
  186.                 layer.SelectionRect = null;
  187.                 SelectStrokesInRect(selectionRect);
  188.             }
  189.             layer.InvalidateVisual();
  190.             e.Pointer.Capture(null);
  191.         }
  192.         private void SelectStrokesInRect(Rect rect)
  193.         {
  194.             selectedStrokes.Clear();
  195.             foreach (var stroke in strokes)
  196.             {
  197.                 if (stroke.Points.Any(p => rect.Contains(p)))
  198.                 {
  199.                     selectedStrokes.Add(stroke);
  200.                 }
  201.             }
  202.             layer.SelectedStrokes = selectedStrokes;
  203.             UpdateSelectionBox();
  204.         }
  205.         private void UpdateSelectionBox()
  206.         {
  207.             if (selectedStrokes.Count == 0)
  208.             {
  209.                 layer.SelectionBox = null;
  210.                 return;
  211.             }
  212.             double minX = double.MaxValue, minY = double.MaxValue;
  213.             double maxX = double.MinValue, maxY = double.MinValue;
  214.             foreach (var stroke in selectedStrokes)
  215.             {
  216.                 foreach (var p in stroke.Points)
  217.                 {
  218.                     minX = Math.Min(minX, p.X);
  219.                     minY = Math.Min(minY, p.Y);
  220.                     maxX = Math.Max(maxX, p.X);
  221.                     maxY = Math.Max(maxY, p.Y);
  222.                 }
  223.             }
  224.             layer.SelectionBox = new Rect(minX, minY, maxX - minX, maxY - minY);
  225.         }
  226.         private void EraseAtPoint(Point point)
  227.         {
  228.             const double hitRadius = 5;
  229.             SaveUndoState();
  230.             strokes.RemoveAll(s => s.Points.Exists(p => Distance(p, point) < hitRadius));
  231.             layer.Strokes = strokes;
  232.             layer.InvalidateVisual();
  233.         }
  234.         private double Distance(Point a, Point b)
  235.         {
  236.             var dx = a.X - b.X;
  237.             var dy = a.Y - b.Y;
  238.             return Math.Sqrt(dx * dx + dy * dy);
  239.         }
  240.         public void MoveSelected(Vector delta)
  241.         {
  242.             foreach (var stroke in selectedStrokes)
  243.             {
  244.                 for (int i = 0; i < stroke.Points.Count; i++)
  245.                     stroke.Points[i] += delta;
  246.             }
  247.             UpdateSelectionBox();
  248.             layer.InvalidateVisual();
  249.         }
  250.         private void SaveUndoState()
  251.         {
  252.             undoStack.Push(strokes.Select(s => new InkStroke
  253.             {
  254.                 Points = new List<Point>(s.Points),
  255.                 Color = s.Color,
  256.                 Thickness = s.Thickness
  257.             }).ToList());
  258.             redoStack.Clear();
  259.             layer.Strokes = strokes;
  260.         }
  261.         public void Undo()
  262.         {
  263.             if (undoStack.Count == 0) return;
  264.             redoStack.Push(strokes);
  265.             strokes = undoStack.Pop();
  266.             layer.Strokes = strokes;
  267.             layer.InvalidateVisual();
  268.         }
  269.         public void Redo()
  270.         {
  271.             if (redoStack.Count == 0) return;
  272.             undoStack.Push(strokes);
  273.             strokes = redoStack.Pop();
  274.             layer.Strokes = strokes;
  275.             layer.InvalidateVisual();
  276.         }
  277.         public IReadOnlyList<InkStroke> Strokes => strokes.AsReadOnly();
  278.     }
  279. }
复制代码
SimpleInkCanvas.axaml代码,其中office.jpg要把属性设置为AvaloniaResource。目前AvaloniaResource除了对axaml有bug外,其他资源是没问题。
  1. <Window xmlns="https://github.com/avaloniaui"
  2.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  3.         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  4.         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  5.         Height="300" Width="300"
  6.         x:Class="AvaloniaUI.SimpleInkCanvas"
  7.         Title="SimpleInkCanvas">
  8.     <Grid RowDefinitions="auto,*">
  9.         <StackPanel Margin="5" Orientation="Horizontal">
  10.             <TextBlock Margin="5">EditingMode: </TextBlock>
  11.             <ComboBox Name="lstEditingMode"  VerticalAlignment="Center">
  12.             </ComboBox>
  13.         </StackPanel>
  14.         <InkCanvas Name="inkCanvas" Grid.Row="1" Background="LightYellow" EditingMode="{Binding ElementName=lstEditingMode,Path=SelectedItem}">
  15.             <Button Canvas.Top="10" Canvas.Left="10">Hello</Button>
  16.             <Image Source="avares://AvaloniaUI/Resources/Images/office.jpg" Canvas.Top="10" Canvas.Left="50"
  17.                Width="100" Height="100"/>
  18.         </InkCanvas>
  19.     </Grid>
  20. </Window>
复制代码
SimpleInkCanvas.axaml.cs代码
  1. using Avalonia;
  2. using Avalonia.Controls;
  3. using Avalonia.Markup.Xaml;
  4. using Shares.Avalonia;
  5. using System;
  6. namespace AvaloniaUI;
  7. public partial class SimpleInkCanvas : Window
  8. {
  9.     public SimpleInkCanvas()
  10.     {
  11.         InitializeComponent();
  12.         foreach (InkEditingMode mode in Enum.GetValues(typeof(InkEditingMode)))
  13.         {
  14.             lstEditingMode.Items.Add(mode);
  15.             lstEditingMode.SelectedItem = inkCanvas.EditingMode;
  16.         }
  17.     }
  18. }
复制代码
运行效果
1.png

 

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