这次继承C# Avalonia官方自带的Canvas,扩展一个InkCanvas,兼容Canvas的所有功能。为了简化自定义命名控件,建议把自定义控件加入到默认空间。
AssemblyInfo.cs代码如下- using System.Runtime.CompilerServices;
- using System.Resources;
- using Avalonia.Metadata;
- [assembly: NeutralResourcesLanguage("zh-CN")]
- [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Shares.Avalonia")]
复制代码 Canvas类有几点需要注意。
1. 自定义内容区域,是通过[Content]属性来描述Controls类。- [Content]
- public Controls Children { get; } = new Controls();
复制代码 2. Render是sealed,所以不支持重写Render。- public sealed override void Render(DrawingContext context)
复制代码 现在,我们在Shares.Avalonia共享项目中,创建一个ControlExtensions.cs,实现InkCanvas类。代码如下- using Avalonia;
- using Avalonia.Controls;
- using Avalonia.Input;
- using Avalonia.Media;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- namespace Shares.Avalonia
- {
- public enum InkEditingMode
- {
- Ink,
- Erase,
- Select
- }
- public class InkStroke
- {
- public List<Point> Points { get; set; } = new();
- public Color Color { get; set; } = Colors.Black;
- public double Thickness { get; set; } = 1.0;
- }
- public class InkCanvasLayer : Control
- {
- public List<InkStroke> Strokes { get; set; } = new();
- public InkStroke? CurrentStroke { get; set; }
- public List<InkStroke> SelectedStrokes { get; set; } = new();
- public Rect? SelectionRect { get; set; }
- public Rect? SelectionBox { get; set; }
- public override void Render(DrawingContext context)
- {
- base.Render(context);
- foreach (var stroke in Strokes)
- {
- var isSelected = SelectedStrokes.Contains(stroke);
- DrawStroke(context, stroke, isSelected);
- }
- if (CurrentStroke != null)
- DrawStroke(context, CurrentStroke);
- if (SelectionRect.HasValue)
- {
- context.DrawRectangle(null,
- new Pen(Brushes.DarkOliveGreen, 1, dashStyle: DashStyle.Dash),
- SelectionRect.Value);
- }
- if (SelectionBox.HasValue)
- {
- var pen = new Pen(Brushes.DarkGray, 1, dashStyle: DashStyle.Dash);
- context.DrawRectangle(null, pen, SelectionBox.Value);
- }
- }
- private void DrawStroke(DrawingContext context, InkStroke stroke, bool isSelected = false)
- {
- if (stroke.Points.Count < 2) return;
- var color = isSelected ? Colors.Black : stroke.Color;
- var thickness = isSelected ? stroke.Thickness * 2 : stroke.Thickness;
- var pen = new Pen(new SolidColorBrush(color), thickness);
- for (int i = 1; i < stroke.Points.Count; i++)
- {
- context.DrawLine(pen, stroke.Points[i - 1], stroke.Points[i]);
- }
- }
- }
- public class InkCanvas : Canvas
- {
- private readonly InkCanvasLayer layer;
- private List<InkStroke> strokes = new();
- private InkStroke? currentStroke;
- private Stack<List<InkStroke>> undoStack = new();
- private Stack<List<InkStroke>> redoStack = new();
- private bool isSelecting = false;
- private Rect selectionRect;
- private List<InkStroke> selectedStrokes = new();
- private Point selectionStart;
- private bool isDraggingSelection = false;
- private Point lastDragPoint;
- public static readonly StyledProperty<Color> StrokeColorProperty =
- AvaloniaProperty.Register<InkCanvas, Color>(nameof(StrokeColor), Colors.Black);
- public Color StrokeColor
- {
- get => GetValue(StrokeColorProperty);
- set => SetValue(StrokeColorProperty, value);
- }
- public static readonly StyledProperty<double> StrokeThicknessProperty =
- AvaloniaProperty.Register<InkCanvas, double>(nameof(StrokeThickness), 2.0);
- public double StrokeThickness
- {
- get => GetValue(StrokeThicknessProperty);
- set => SetValue(StrokeThicknessProperty, value);
- }
- public static readonly StyledProperty<InkEditingMode> EditingModeProperty =
- AvaloniaProperty.Register<InkCanvas, InkEditingMode>(nameof(EditingMode), InkEditingMode.Ink);
- public InkEditingMode EditingMode
- {
- get => GetValue(EditingModeProperty);
- set => SetValue(EditingModeProperty, value);
- }
- public InkCanvas()
- {
- layer = new InkCanvasLayer();
- Children.Add(layer);
- PointerPressed += OnPointerPressed;
- PointerMoved += OnPointerMoved;
- PointerReleased += OnPointerReleased;
- this.GetObservable(EditingModeProperty).Subscribe(mode =>
- {
- selectedStrokes.Clear();
- layer.SelectedStrokes = selectedStrokes;
- layer.SelectionBox = null;
- layer.InvalidateVisual();
- });
- Background = Brushes.White;
- }
- protected override Size ArrangeOverride(Size finalSize)
- {
- layer.Arrange(new Rect(finalSize));
- return base.ArrangeOverride(finalSize);
- }
- private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
- {
- var point = e.GetPosition(this);
- if (EditingMode == InkEditingMode.Erase)
- {
- EraseAtPoint(point);
- return;
- }
- if (EditingMode == InkEditingMode.Select)
- {
- selectionStart = point;
- selectionRect = new Rect(point, point);
- if (selectedStrokes.Any(s => s.Points.Any(p => Distance(p, point) < 5)))
- {
- isDraggingSelection = true;
- lastDragPoint = point;
- return;
- }
- isSelecting = true;
- return;
- }
- currentStroke = new InkStroke
- {
- Color = StrokeColor,
- Thickness = StrokeThickness
- };
- currentStroke.Points.Add(point);
- layer.CurrentStroke = currentStroke;
- e.Pointer.Capture(this);
- }
- private void OnPointerMoved(object? sender, PointerEventArgs e)
- {
- var point = e.GetPosition(this);
- if (currentStroke != null && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
- {
- currentStroke.Points.Add(point);
- layer.InvalidateVisual();
- }
- if (isDraggingSelection && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
- {
- var delta = point - lastDragPoint;
- MoveSelected(delta);
- lastDragPoint = point;
- UpdateSelectionBox();
- }
- if (isSelecting && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
- {
- selectionRect = new Rect(selectionStart, point).Normalize();
- layer.SelectionRect = selectionRect;
- layer.InvalidateVisual();
- }
- }
- private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
- {
- if (currentStroke != null)
- {
- SaveUndoState();
- strokes.Add(currentStroke);
- currentStroke = null;
- layer.CurrentStroke = null;
- }
- if (isDraggingSelection)
- {
- isDraggingSelection = false;
- }
- if (isSelecting)
- {
- isSelecting = false;
- layer.SelectionRect = null;
- SelectStrokesInRect(selectionRect);
- }
- layer.InvalidateVisual();
- e.Pointer.Capture(null);
- }
- private void SelectStrokesInRect(Rect rect)
- {
- selectedStrokes.Clear();
- foreach (var stroke in strokes)
- {
- if (stroke.Points.Any(p => rect.Contains(p)))
- {
- selectedStrokes.Add(stroke);
- }
- }
- layer.SelectedStrokes = selectedStrokes;
- UpdateSelectionBox();
- }
- private void UpdateSelectionBox()
- {
- if (selectedStrokes.Count == 0)
- {
- layer.SelectionBox = null;
- return;
- }
- double minX = double.MaxValue, minY = double.MaxValue;
- double maxX = double.MinValue, maxY = double.MinValue;
- foreach (var stroke in selectedStrokes)
- {
- foreach (var p in stroke.Points)
- {
- minX = Math.Min(minX, p.X);
- minY = Math.Min(minY, p.Y);
- maxX = Math.Max(maxX, p.X);
- maxY = Math.Max(maxY, p.Y);
- }
- }
- layer.SelectionBox = new Rect(minX, minY, maxX - minX, maxY - minY);
- }
- private void EraseAtPoint(Point point)
- {
- const double hitRadius = 5;
- SaveUndoState();
- strokes.RemoveAll(s => s.Points.Exists(p => Distance(p, point) < hitRadius));
- layer.Strokes = strokes;
- layer.InvalidateVisual();
- }
- private double Distance(Point a, Point b)
- {
- var dx = a.X - b.X;
- var dy = a.Y - b.Y;
- return Math.Sqrt(dx * dx + dy * dy);
- }
- public void MoveSelected(Vector delta)
- {
- foreach (var stroke in selectedStrokes)
- {
- for (int i = 0; i < stroke.Points.Count; i++)
- stroke.Points[i] += delta;
- }
- UpdateSelectionBox();
- layer.InvalidateVisual();
- }
- private void SaveUndoState()
- {
- undoStack.Push(strokes.Select(s => new InkStroke
- {
- Points = new List<Point>(s.Points),
- Color = s.Color,
- Thickness = s.Thickness
- }).ToList());
- redoStack.Clear();
- layer.Strokes = strokes;
- }
- public void Undo()
- {
- if (undoStack.Count == 0) return;
- redoStack.Push(strokes);
- strokes = undoStack.Pop();
- layer.Strokes = strokes;
- layer.InvalidateVisual();
- }
- public void Redo()
- {
- if (redoStack.Count == 0) return;
- undoStack.Push(strokes);
- strokes = redoStack.Pop();
- layer.Strokes = strokes;
- layer.InvalidateVisual();
- }
- public IReadOnlyList<InkStroke> Strokes => strokes.AsReadOnly();
- }
- }
复制代码 SimpleInkCanvas.axaml代码,其中office.jpg要把属性设置为AvaloniaResource。目前AvaloniaResource除了对axaml有bug外,其他资源是没问题。- <Window xmlns="https://github.com/avaloniaui"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- Height="300" Width="300"
- x:Class="AvaloniaUI.SimpleInkCanvas"
- Title="SimpleInkCanvas">
- <Grid RowDefinitions="auto,*">
- <StackPanel Margin="5" Orientation="Horizontal">
- <TextBlock Margin="5">EditingMode: </TextBlock>
- <ComboBox Name="lstEditingMode" VerticalAlignment="Center">
- </ComboBox>
- </StackPanel>
- <InkCanvas Name="inkCanvas" Grid.Row="1" Background="LightYellow" EditingMode="{Binding ElementName=lstEditingMode,Path=SelectedItem}">
- <Button Canvas.Top="10" Canvas.Left="10">Hello</Button>
- <Image Source="avares://AvaloniaUI/Resources/Images/office.jpg" Canvas.Top="10" Canvas.Left="50"
- Width="100" Height="100"/>
- </InkCanvas>
- </Grid>
- </Window>
复制代码 SimpleInkCanvas.axaml.cs代码- using Avalonia;
- using Avalonia.Controls;
- using Avalonia.Markup.Xaml;
- using Shares.Avalonia;
- using System;
- namespace AvaloniaUI;
- public partial class SimpleInkCanvas : Window
- {
- public SimpleInkCanvas()
- {
- InitializeComponent();
- foreach (InkEditingMode mode in Enum.GetValues(typeof(InkEditingMode)))
- {
- lstEditingMode.Items.Add(mode);
- lstEditingMode.SelectedItem = inkCanvas.EditingMode;
- }
- }
- }
复制代码 运行效果
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |