<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>为了实现这个效果,在Canvas上使用了三个隧道事件(预览事件)PreviewMouseLeftButtonDown、PreviewMouseMove、PreviewMouseLeftButtonUp。
private bool _isDown; private bool _isDragging; private UIElement _originalElement; private double _originalLeft; private double _originalTop; private SimpleCircleAdorner _overlayElement; private Point _startPoint;定义了这几个私有字段。
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; } }最开始引发这个事件的是MyCanvas元素,当事件源是Canvas的时候,不做处理,因为我们只想处理发生在MyCanvas子元素上的鼠标左键按下事件。
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(); } } }鼠标左键已经按下了,但还没开始移动事,执行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);创建了一个新的装饰器(Adorner)并将其与一个特定的UI元素关联起来。
4.以视觉方式遮盖或覆盖 UIElement 的一部分或全部。
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); // 堆代码 duidaima.com // 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);这里在元素内部添加了动画。
// 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);这里在元素的四个角画了小圆形。
var layer = AdornerLayer.GetAdornerLayer(_originalElement); layer.Add(_overlayElement);这段代码的作用是将之前创建的装饰器_overlayElement添加到与特定UI元素_originalElement相关联的装饰器层(AdornerLayer)中。一旦装饰器被添加到装饰器层中,它就会在_originalElement被渲染时显示出来。AdornerLayer是一个特殊的层,用于在UI元素上绘制装饰器。每个UI元素都有一个与之关联的装饰器层,但并不是所有的UI元素都能直接看到这个层。
private void DragMoved() { var currentPosition = Mouse.GetPosition(MyCanvas); _overlayElement.LeftOffset = currentPosition.X - _startPoint.X; _overlayElement.TopOffset = currentPosition.Y - _startPoint.Y; }计算元素的偏移。
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);