Реализация Rubberband в WPF

Rubberband, оно же «прямоугольное выделение» (но это не точно), можно использовать множеством способов. Ниже описан простейший способ, не подразумевающий какую-либо сложную обработку — координаты и размеры выделенной области просто передаются элементу управления.
Инструмент прямоугольного выделения реализован с помощью AdornerLayer, позволяющего рисовать поверх декорируемого элемента управления.
Элемент управления (декорируемый элемент) и его программный интерфейс:
  1. public interface ISpanable {
  2.   void Span(Rect region);
  3. }
  4.  
  5. // Декорируемый компонент. Может быть унаследован от класса UIElement либо от одного из его потомков, e.g. Canvas, DockPanel, DataGrid, etc.
  6. public class SpanableCanvas : Canvas, ISpanable { ... }
Декоративный элемент (адорнер) Rubberband:
  1. public class Rubberband : Adorner {
  2.   private Point startPoint;
  3.   private Point currentPoint;
  4.   private bool isActive;
  5.  
  6.   private readonly Brush fill;
  7.   private readonly Pen borderPen;
  8.  
  9.   public Rubberband(UIElement adornedElement)
  10.   : base(adornedElement) {
  11.     adornedElement.MouseLeftButtonDown += OnLeftButtonDown;
  12.     adornedElement.MouseMove += OnMove;
  13.     adornedElement.PreviewMouseLeftButtonUp += OnPreviewLeftButtonUp;
  14.  
  15.     fill = new SolidColorBrush(SystemColors.HighlightColor) {
  16.       Opacity = .3
  17.     };
  18.     borderPen = new Pen(SystemColors.HighlightBrush, 1);
  19.   }
  20.  
  21.   private void OnLeftButtonDown(object sender, MouseButtonEventArgs args) {
  22.     // Запоминаем позицию левой верхней точки области выделения
  23.     startPoint = Mouse.GetPosition(AdornedElement);
  24.     currentPoint = startPoint;
  25.     Mouse.Capture(AdornedElement);
  26.     isActive = true;
  27.   }
  28.  
  29.   private void OnMove(object sender, MouseEventArgs args) {
  30.     if (args.LeftButton != MouseButtonState.Pressed || !isActive) return;
  31.     currentPoint = Mouse.GetPosition(AdornedElement);
  32.     // Не рисовать за пределами «декорируемого» компонента
  33.     currentPoint.X = Math.Max(0, Math.Min(currentPoint.X, AdornedElement.RenderSize.Width));
  34.     currentPoint.Y = Math.Max(0, Math.Min(currentPoint.Y, AdornedElement.RenderSize.Height));
  35.     InvalidateVisual();
  36.   }
  37.  
  38.   private void OnPreviewLeftButtonUp(object sender, MouseButtonEventArgs args) {
  39.     // Область выделения
  40.     Rect region = new Rect(startPoint, currentPoint);
  41.     // Сброс состояния адорнера
  42.     DisposeRubberband();
  43.     if (AdornedElement is ISpanable) {
  44.       // Игнорировать, если выделенная область слишком маленькая (в т.ч. если было одиночное нажатие ЛКМ)
  45.       if (region.Width <= double.Epsilon || region.Height <= double.Epsilon) return;
  46.       ((ISpanable)AdornedElement)?.Span(region);
  47.     }
  48.   }
  49.  
  50.   private void DisposeRubberband() {
  51.     currentPoint = new Point(0, 0);
  52.     startPoint = new Point(0, 0);
  53.  
  54.     adornedElement.MouseLeftButtonDown -= OnLeftButtonDown;
  55.     adornedElement.MouseMove -= OnMove;
  56.     adornedElement.PreviewMouseLeftButtonUp -= OnPreviewLeftButtonUp;
  57.     AdornedElement.ReleaseMouseCapture);
  58.     InvalidateVisual();
  59.     isActive = false;
  60.   }
  61.  
  62.   protected override void OnRender(DrawingContext ctx) {
  63.     Rect rect = new Rect(startPoint, currentPoint);
  64.     ctx.DrawGeometry(fill, borderPen, new RectangleGeometry(rect));
  65.     base.OnRender(ctx);
  66.   }
  67. }
Подключаемое поведение. Зависит от `System.Windows.Interactivity` (входит в поставку .NET Framework).
  1. // «Поведение», которое можно подключить к любому объекту типа UIElement
  2. public class RubberbandBehaviour : Behavior<SpanableCanvas> {
  3.   protected override void OnAttached() {
  4.     AssociatedObject.Loaded += OnLoaded;
  5.     base.OnAttached();
  6.   }
  7.  
  8.   private void OnLoaded(object sender, RoutedEventArgs args) {
  9.     Rubberband rubberband = new Rubberband(AssociatedObject);
  10.     AdornerLayer layer = AdornerLayer.GetAdornerLayer(AssociatedObject);
  11.     layer.Add(adorner);
  12.   }
  13.  
  14.   protected override void OnDetaching() {
  15.     AssociatedObject.Loaded -= OnLoaded;
  16.     base.OnDetaching();
  17.   }
  18. }
Подключение поведения:
  1. Interaction.GetBehaviors(spanableCanvas).Add(new RubberbandBehaviour());

Реклама

Мы в соцсетях

tw tg yt gt