Реализация Rubberband в WPF
Rubberband, оно же «прямоугольное выделение» (но это не точно), можно использовать множеством способов. Ниже описан простейший способ, не подразумевающий какую-либо сложную обработку — координаты и размеры выделенной области просто передаются элементу управления.
Инструмент прямоугольного выделения реализован с помощью AdornerLayer, позволяющего рисовать поверх декорируемого элемента управления.
Инструмент прямоугольного выделения реализован с помощью AdornerLayer, позволяющего рисовать поверх декорируемого элемента управления.
Элемент управления (декорируемый элемент) и его программный интерфейс:
- public interface ISpanable {
- void Span(Rect region);
- }
- // Декорируемый компонент. Может быть унаследован от класса UIElement либо от одного из его потомков, e.g. Canvas, DockPanel, DataGrid, etc.
- public class SpanableCanvas : Canvas, ISpanable { ... }
Декоративный элемент (адорнер) Rubberband:
- public class Rubberband : Adorner {
- private Point startPoint;
- private Point currentPoint;
- private bool isActive;
- private readonly Brush fill;
- private readonly Pen borderPen;
- public Rubberband(UIElement adornedElement)
- : base(adornedElement) {
- adornedElement.MouseLeftButtonDown += OnLeftButtonDown;
- adornedElement.MouseMove += OnMove;
- adornedElement.PreviewMouseLeftButtonUp += OnPreviewLeftButtonUp;
- fill = new SolidColorBrush(SystemColors.HighlightColor) {
- Opacity = .3
- };
- borderPen = new Pen(SystemColors.HighlightBrush, 1);
- }
- private void OnLeftButtonDown(object sender, MouseButtonEventArgs args) {
- // Запоминаем позицию левой верхней точки области выделения
- startPoint = Mouse.GetPosition(AdornedElement);
- currentPoint = startPoint;
- Mouse.Capture(AdornedElement);
- isActive = true;
- }
- private void OnMove(object sender, MouseEventArgs args) {
- if (args.LeftButton != MouseButtonState.Pressed || !isActive) return;
- currentPoint = Mouse.GetPosition(AdornedElement);
- // Не рисовать за пределами «декорируемого» компонента
- currentPoint.X = Math.Max(0, Math.Min(currentPoint.X, AdornedElement.RenderSize.Width));
- currentPoint.Y = Math.Max(0, Math.Min(currentPoint.Y, AdornedElement.RenderSize.Height));
- InvalidateVisual();
- }
- private void OnPreviewLeftButtonUp(object sender, MouseButtonEventArgs args) {
- // Область выделения
- Rect region = new Rect(startPoint, currentPoint);
- // Сброс состояния адорнера
- DisposeRubberband();
- if (AdornedElement is ISpanable) {
- // Игнорировать, если выделенная область слишком маленькая (в т.ч. если было одиночное нажатие ЛКМ)
- if (region.Width <= double.Epsilon || region.Height <= double.Epsilon) return;
- ((ISpanable)AdornedElement)?.Span(region);
- }
- }
- private void DisposeRubberband() {
- currentPoint = new Point(0, 0);
- startPoint = new Point(0, 0);
- adornedElement.MouseLeftButtonDown -= OnLeftButtonDown;
- adornedElement.MouseMove -= OnMove;
- adornedElement.PreviewMouseLeftButtonUp -= OnPreviewLeftButtonUp;
- AdornedElement.ReleaseMouseCapture);
- InvalidateVisual();
- isActive = false;
- }
- protected override void OnRender(DrawingContext ctx) {
- Rect rect = new Rect(startPoint, currentPoint);
- ctx.DrawGeometry(fill, borderPen, new RectangleGeometry(rect));
- base.OnRender(ctx);
- }
- }
Подключаемое поведение. Зависит от `System.Windows.Interactivity` (входит в поставку .NET Framework).
- // «Поведение», которое можно подключить к любому объекту типа UIElement
- public class RubberbandBehaviour : Behavior<SpanableCanvas> {
- protected override void OnAttached() {
- AssociatedObject.Loaded += OnLoaded;
- base.OnAttached();
- }
- private void OnLoaded(object sender, RoutedEventArgs args) {
- Rubberband rubberband = new Rubberband(AssociatedObject);
- AdornerLayer layer = AdornerLayer.GetAdornerLayer(AssociatedObject);
- layer.Add(adorner);
- }
- protected override void OnDetaching() {
- AssociatedObject.Loaded -= OnLoaded;
- base.OnDetaching();
- }
- }
Подключение поведения:
- Interaction.GetBehaviors(spanableCanvas).Add(new RubberbandBehaviour());