前言
在Canvas中放置了一些元素,需要能夠拖拉這些元素,在WPF Samples中的DragDropObjects項(xiàng)目中告訴了我們?nèi)绾螌?shí)現(xiàn)這種效果。
效果如下所示:
拖拉過程中的效果如下所示:
具體實(shí)現(xiàn)
xaml頁面
我們先來看看xaml:
<Canvas Name="MyCanvas"
PreviewMouseLeftButtonDown="MyCanvas_PreviewMouseLeftButtonDown"
PreviewMouseMove="MyCanvas_PreviewMouseMove"
PreviewMouseLeftButtonUp="MyCanvas_PreviewMouseLeftButtonUp">
<Rectangle Fill="Blue" Height="32" Width="32" Canvas.Top="8" Canvas.Left="8"/>
<TextBox Text="This is a TextBox. Drag and drop me" Canvas.Top="100" Canvas.Left="100"/>
</Canvas>
為了實(shí)現(xiàn)這個(gè)效果,在Canvas上使用了三個(gè)隧道事件(預(yù)覽事件)PreviewMouseLeftButtonDown
、PreviewMouseMove
、PreviewMouseLeftButtonUp
。
而什么是隧道事件(預(yù)覽事件)呢?
預(yù)覽事件,也稱為隧道事件,是從應(yīng)用程序根元素向下遍歷元素樹到引發(fā)事件的元素的路由事件。
PreviewMouseLeftButtonDown
當(dāng)用戶按下鼠標(biāo)左鍵時(shí)觸發(fā)。
PreviewMouseMove
當(dāng)用戶移動(dòng)鼠標(biāo)時(shí)觸發(fā)。
PreviewMouseLeftButtonUp
當(dāng)用戶釋放鼠標(biāo)左鍵時(shí)觸發(fā)。
再來看看cs:
private bool _isDown;
private bool _isDragging;
private UIElement _originalElement;
private double _originalLeft;
private double _originalTop;
private SimpleCircleAdorner _overlayElement;
private Point _startPoint;
定義了這幾個(gè)私有字段。
鼠標(biāo)左鍵按下事件處理程序
鼠標(biāo)左鍵按下事件處理程序:
private void MyCanvas_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.Source == MyCanvas)
{
}
else
{
_isDown = true;
_startPoint = e.GetPosition(MyCanvas);
_originalElement = e.Source as UIElement;
MyCanvas.CaptureMouse();
e.Handled = true;
}
}
最開始引發(fā)這個(gè)事件的是MyCanvas元素,當(dāng)事件源是Canvas的時(shí)候,不做處理,因?yàn)槲覀冎幌胩幚戆l(fā)生在MyCanvas子元素上的鼠標(biāo)左鍵按下事件。
鼠標(biāo)移動(dòng)事件處理程序
現(xiàn)在來看看鼠標(biāo)移動(dòng)事件處理程序:
private void MyCanvas_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (_isDown)
{
if ((_isDragging == false) &&
((Math.Abs(e.GetPosition(MyCanvas).X - _startPoint.X) >
SystemParameters.MinimumHorizontalDragDistance) ||
(Math.Abs(e.GetPosition(MyCanvas).Y - _startPoint.Y) >
SystemParameters.MinimumVerticalDragDistance)))
{
DragStarted();
}
if (_isDragging)
{
DragMoved();
}
}
}
鼠標(biāo)左鍵已經(jīng)按下了,但還沒開始移動(dòng)事,執(zhí)行DragStarted方法。
創(chuàng)建裝飾器
DragStarted方法如下:
private void DragStarted()
{
_isDragging = true;
_originalLeft = Canvas.GetLeft(_originalElement);
_originalTop = Canvas.GetTop(_originalElement);
_overlayElement = new SimpleCircleAdorner(_originalElement);
var layer = AdornerLayer.GetAdornerLayer(_originalElement);
layer.Add(_overlayElement);
}
_overlayElement = new SimpleCircleAdorner(_originalElement);
創(chuàng)建了一個(gè)新的裝飾器(Adorner)并將其與一個(gè)特定的UI元素關(guān)聯(lián)起來。
而WPF中裝飾器是什么呢?
裝飾器是一種特殊類型的 FrameworkElement,用于向用戶提供視覺提示。 裝飾器有很多用途,可用來向元素添加功能句柄,或者提供有關(guān)某個(gè)控件的狀態(tài)信息。
Adorner 是綁定到 UIElement 的自定義 FrameworkElement。 裝飾器在 AdornerLayer 中呈現(xiàn),它是始終位于裝飾元素或裝飾元素集合之上的呈現(xiàn)表面。 裝飾器的呈現(xiàn)獨(dú)立于裝飾器綁定到的 UIElement 的呈現(xiàn)。 裝飾器通常使用位于裝飾元素左上部的標(biāo)準(zhǔn) 2D 坐標(biāo)原點(diǎn),相對(duì)于其綁定到的元素進(jìn)行定位。
裝飾器的常見應(yīng)用包括:
向 UIElement 添加功能句柄,使用戶能夠以某種方式操作元素(調(diào)整大小、旋轉(zhuǎn)、重新定位等)。
提供視覺反饋以指示各種狀態(tài),或者響應(yīng)各種事件。
在 UIElement 上疊加視覺裝飾。
以視覺方式遮蓋或覆蓋 UIElement 的一部分或全部。
Windows Presentation Foundation (WPF) 為裝飾視覺元素提供了一個(gè)基本框架。
在這個(gè)Demo中裝飾器就是移動(dòng)過程中四個(gè)角上出現(xiàn)的小圓以及內(nèi)部不斷閃爍的顏色,如下所示:
這是如何實(shí)現(xiàn)的呢?
這個(gè)Demo中自定義了一個(gè)繼承自Adorner的SimpleCircleAdorner,代碼如下所示:
using System;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace DragDropObjects
{
public class SimpleCircleAdorner : Adorner
{
private readonly Rectangle _child;
private double _leftOffset;
private double _topOffset;
// Be sure to call the base class constructor.
public SimpleCircleAdorner(UIElement adornedElement)
: base(adornedElement)
{
var brush = new VisualBrush(adornedElement);
_child = new Rectangle
{
Width = adornedElement.RenderSize.Width,
Height = adornedElement.RenderSize.Height
};
var animation = new DoubleAnimation(0.3, 1, new Duration(TimeSpan.FromSeconds(1)))
{
AutoReverse = true,
RepeatBehavior = RepeatBehavior.Forever
};
brush.BeginAnimation(Brush.OpacityProperty, animation);
_child.Fill = brush;
}
protected override int VisualChildrenCount => 1;
public double LeftOffset
{
get { return _leftOffset; }
set
{
_leftOffset = value;
UpdatePosition();
}
}
public double TopOffset
{
get { return _topOffset; }
set
{
_topOffset = value;
UpdatePosition();
}
}
// A common way to implement an adorner's rendering behavior is to override the OnRender
// method, which is called by the layout subsystem as part of a rendering pass.
protected override void OnRender(DrawingContext drawingContext)
{
// Get a rectangle that represents the desired size of the rendered element
// after the rendering pass. This will be used to draw at the corners of the
// adorned element.
var adornedElementRect = new Rect(AdornedElement.DesiredSize);
// Some arbitrary drawing implements.
var renderBrush = new SolidColorBrush(Colors.Green) {Opacity = 0.2};
var renderPen = new Pen(new SolidColorBrush(Colors.Navy), 1.5);
const double renderRadius = 5.0;
// Just draw a circle at each corner.
drawingContext.DrawRectangle(renderBrush, renderPen, adornedElementRect);
drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.TopLeft, renderRadius, renderRadius);
drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.TopRight, renderRadius, renderRadius);
drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.BottomLeft, renderRadius, renderRadius);
drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.BottomRight, renderRadius,
renderRadius);
}
protected override Size MeasureOverride(Size constraint)
{
_child.Measure(constraint);
return _child.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
_child.Arrange(new Rect(finalSize));
return finalSize;
}
protected override Visual GetVisualChild(int index) => _child;
private void UpdatePosition()
{
var adornerLayer = Parent as AdornerLayer;
adornerLayer?.Update(AdornedElement);
}
public override GeneralTransform GetDesiredTransform(GeneralTransform transform)
{
var result = new GeneralTransformGroup();
result.Children.Add(base.GetDesiredTransform(transform));
result.Children.Add(new TranslateTransform(_leftOffset, _topOffset));
return result;
}
}
}
var animation = new DoubleAnimation(0.3, 1, new Duration(TimeSpan.FromSeconds(1)))
{
AutoReverse = true,
RepeatBehavior = RepeatBehavior.Forever
};
brush.BeginAnimation(Brush.OpacityProperty, animation);
這里在元素內(nèi)部添加了動(dòng)畫。
// Just draw a circle at each corner.
drawingContext.DrawRectangle(renderBrush, renderPen, adornedElementRect);
drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.TopLeft, renderRadius, renderRadius);
drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.TopRight, renderRadius, renderRadius);
drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.BottomLeft, renderRadius, renderRadius);
drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.BottomRight, renderRadius,
renderRadius);
這里在元素的四個(gè)角畫了小圓形。
var layer = AdornerLayer.GetAdornerLayer(_originalElement);
layer.Add(_overlayElement);
這段代碼的作用是將之前創(chuàng)建的裝飾器_overlayElement
添加到與特定UI元素_originalElement
相關(guān)聯(lián)的裝飾器層(AdornerLayer)中。一旦裝飾器被添加到裝飾器層中,它就會(huì)在_originalElement
被渲染時(shí)顯示出來。
AdornerLayer
是一個(gè)特殊的層,用于在UI元素上繪制裝飾器。每個(gè)UI元素都有一個(gè)與之關(guān)聯(lián)的裝飾器層,但并不是所有的UI元素都能直接看到這個(gè)層。
GetAdornerLayer方法會(huì)返回與_originalElement相關(guān)聯(lián)的裝飾器層。
裝飾器層會(huì)負(fù)責(zé)管理裝飾器的渲染和布局,確保裝飾器正確地顯示在UI元素上。
再來看看DragMoved方法:
private void DragMoved()
{
var currentPosition = Mouse.GetPosition(MyCanvas);
_overlayElement.LeftOffset = currentPosition.X - _startPoint.X;
_overlayElement.TopOffset = currentPosition.Y - _startPoint.Y;
}
計(jì)算元素的偏移。
鼠標(biāo)左鍵松開事件處理程序
鼠標(biāo)左鍵松開事件處理程序:
private void MyCanvas_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (_isDown)
{
DragFinished();
e.Handled = true;
}
}
DragFinished方法如下:
private void DragFinished(bool cancelled = false)
{
Mouse.Capture(null);
if (_isDragging)
{
AdornerLayer.GetAdornerLayer(_overlayElement.AdornedElement).Remove(_overlayElement);
if (cancelled == false)
{
Canvas.SetTop(_originalElement, _originalTop + _overlayElement.TopOffset);
Canvas.SetLeft(_originalElement, _originalLeft + _overlayElement.LeftOffset);
}
_overlayElement = null;
}
_isDragging = false;
_isDown = false;
}
AdornerLayer.GetAdornerLayer(_overlayElement.AdornedElement).Remove(_overlayElement);
從與_overlayElement
所裝飾的UI元素相關(guān)聯(lián)的裝飾器層中移除_overlayElement
,從而使得裝飾器不再顯示在UI元素上。這樣,當(dāng)UI元素被渲染時(shí),裝飾器將不再影響其外觀或行為。
代碼來源
[WPF-Samples/Drag and Drop/DragDropObjects at main · microsoft/WPF-Samples (github.com)](https://github.com/microsoft/WPF-Samples/tree/main/Drag and Drop/DragDropObjects)
參考
1、預(yù)覽事件 - WPF .NET | Microsoft Learn
2、裝飾器概述 - WPF .NET Framework | Microsoft Learn
3、Adorner 類 (System.Windows.Documents) | Microsoft Learn
轉(zhuǎn)自https://www.cnblogs.com/mingupupu/p/18270547
該文章在 2024/9/6 9:44:55 編輯過