如何在画布内拖动用户控件
Posted
技术标签:
【中文标题】如何在画布内拖动用户控件【英文标题】:How to drag a UserControl inside a Canvas 【发布时间】:2010-12-02 11:56:29 【问题描述】:我有一个 Canvas,用户可以在其中添加包含表单的 UserControl 子类。用户应该能够在 Canvas 周围拖动这些 UserControl。
使用 WPF 执行此操作的最佳做法是什么?
【问题讨论】:
【参考方案1】:这是在 Silverlight 中完成的,而不是在 WPF 中完成的,但它的工作原理应该相同。
在控件上创建两个私有属性:
protected bool isDragging;
private Point clickPosition;
然后在控件的构造函数中附加一些事件处理函数:
this.MouseLeftButtonDown += new MouseButtonEventHandler(Control_MouseLeftButtonDown);
this.MouseLeftButtonUp += new MouseButtonEventHandler(Control_MouseLeftButtonUp);
this.MouseMove += new MouseEventHandler(Control_MouseMove);
现在创建这些方法:
private void Control_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
isDragging = true;
var draggableControl = sender as UserControl;
clickPosition = e.GetPosition(this);
draggableControl.CaptureMouse();
private void Control_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
isDragging = false;
var draggable = sender as UserControl;
draggable.ReleaseMouseCapture();
private void Control_MouseMove(object sender, MouseEventArgs e)
var draggableControl = sender as UserControl;
if (isDragging && draggableControl != null)
Point currentPosition = e.GetPosition(this.Parent as UIElement);
var transform = draggableControl.RenderTransform as TranslateTransform;
if (transform == null)
transform = new TranslateTransform();
draggableControl.RenderTransform = transform;
transform.X = currentPosition.X - clickPosition.X;
transform.Y = currentPosition.Y - clickPosition.Y;
这里有几点需要注意: 1. 这不必在画布中。它可以在堆栈面板中,也可以在网格中。 2.这使得整个控件可拖动,这意味着如果您单击控件中的任意位置并拖动它将拖动整个控件。不确定这是否正是您想要的。
编辑- 扩展您问题中的一些细节: 我实现这一点的最佳方法是创建一个继承自 UserControl 的类,可能称为 DraggableControl,它是使用此代码构建的,然后所有可拖动控件都应扩展 DraggableControl。
编辑 2 - 当您在此控件中有一个数据网格时存在一个小问题。如果您对数据网格中的列进行排序,则 MouseLeftButtonUp 事件永远不会触发。我已经更新了代码,以便保护 isDragging。我发现最好的解决方案是将此匿名方法与数据网格的 LostMouseCapture 事件联系起来:
this.MyDataGrid.LostMouseCapture += (sender, e) => this.isDragging = false; ;
【讨论】:
很好的答案!正是我搜索的内容。复制和粘贴,它工作。而且比我的尝试更优雅。我希望这对懒猴也有帮助;) 干得好!随着鼠标移动处理程序的一些细微变化,它可以完美运行。非常感谢! @loris:我很想知道您需要做出哪些改变以及为什么做出这些改变。我已经在几个项目中使用了这个确切的代码,并且效果很好。也许我忽略了一些东西。 这对我很有帮助。非常感谢您发布此内容。 在Control_MouseLeftButtonDown
中,我发现需要将clickPosition = e.GetPosition(this);
替换为clickPosition = e.GetPosition(this.Parent as UIElement);
。在进行更改之前,我单击的元素会向下和向右跳(因为它在元素本身的范围内而不是托管画布的范围内获得起始位置)。【参考方案2】:
Corey 的回答大部分是正确的,但它缺少一个关键要素:记住最后一次转换是什么。否则,当您移动一个项目时,松开鼠标按钮,然后再次单击该项目,变换将重置为(0,0)
,并且控件会跳回其原点。
这是一个对我有用的稍微修改过的版本:
public partial class DragItem : UserControl
protected Boolean isDragging;
private Point mousePosition;
private Double prevX, prevY;
public DragItem()
InitializeComponent();
private void UserControl_MouseLeftButtonDown(Object sender, MouseButtonEventArgs e)
isDragging = true;
var draggableControl = (sender as UserControl);
mousePosition = e.GetPosition(Parent as UIElement);
draggableControl.CaptureMouse();
private void UserControl_MouseLeftButtonUp(Object sender, MouseButtonEventArgs e)
isDragging = false;
var draggable = (sender as UserControl);
var transform = (draggable.RenderTransform as TranslateTransform);
if (transform != null)
prevX = transform.X;
prevY = transform.Y;
draggable.ReleaseMouseCapture();
private void UserControl_MouseMove(Object sender, MouseEventArgs e)
var draggableControl = (sender as UserControl);
if (isDragging && draggableControl != null)
var currentPosition = e.GetPosition(Parent as UIElement);
var transform = (draggableControl.RenderTransform as TranslateTransform);
if (transform == null)
transform = new TranslateTransform();
draggableControl.RenderTransform = transform;
transform.X = (currentPosition.X - mousePosition.X);
transform.Y = (currentPosition.Y - mousePosition.Y);
if (prevX > 0)
transform.X += prevX;
transform.Y += prevY;
关键是存储之前的 X 和 Y 偏移量,然后使用它们来增加当前运动的偏移量,以达到正确的聚合偏移量。
【讨论】:
第三次点击正在移动的对象时失败。但是,如果删除 MouseMove 事件中的最后一个 IF 条件,它就可以正常工作。【参考方案3】:如果有人想尝试最小的解决方案,这里是使用MouseMove
事件的解决方案。
布局
<Canvas
Background='Beige'
Name='canvas'>
<Rectangle
Width='50'
Height='50'
Fill='LightPink'
Canvas.Left='350'
Canvas.Top='175'
MouseMove='Rectangle_MouseMove' />
</Canvas>
背后的代码
void OnMouseMove(object sender, MouseEventArgs e)
if (e.Source is Shape shape)
if (e.LeftButton == MouseButtonState.Pressed)
Point p = e.GetPosition(canvas);
Canvas.SetLeft(shape, p.X - shape.ActualWidth / 2);
Canvas.SetTop(shape, p.Y - shape.ActualHeight / 2);
shape.CaptureMouse();
else
shape.ReleaseMouseCapture();
【讨论】:
我怎么强调都不为过,有时最简单的解决方案是最好的。这很有效!谢谢:D【参考方案4】:关于 Corey Sunwold 解决方案 - 我摆脱了 MouseUp 和 MouseDown 事件,并使用 MouseButtonState 简化了 MouseMove 方法,如下所示:) 我正在使用 Canvas.SetLeft()和 Canvas.SetTop() 代替 RenderTransform 所以我不需要存储 MouseDown 事件的旧位置。
if (e.LeftButton == MouseButtonState.Pressed && draggableControl != null)
//...
【讨论】:
酷,我只是想知道为什么我的控件一直跳回原来的位置!!你在我问之前回答了我的问题。+1 实际上如何在没有 MouseDown 事件的情况下获得移动的 X 和 Y 距离? 我投了反对票,因为您没有提供代码。我和邓肯遇到了同样的问题,显然你找到了解决方案....你只是没有表现出来。【参考方案5】:我在给定的解决方案上遇到了一些问题,最终得到了这个:
public partial class UserControlDraggable : UserControl
public UserControlDraggable()
InitializeComponent();
MouseLeftButtonDown += new MouseButtonEventHandler(Control_MouseLeftButtonDown);
MouseLeftButtonUp += new MouseButtonEventHandler(Control_MouseLeftButtonUp);
MouseMove += new MouseEventHandler(Control_MouseMove);
private void Control_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
_isDragging = true;
_mouseLocationWithinMe = e.GetPosition(this);
CaptureMouse();
private void Control_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
_isDragging = false;
this.ReleaseMouseCapture();
private void Control_MouseMove(object sender, MouseEventArgs e)
if (_isDragging)
var mouseWithinParent = e.GetPosition(Parent as UIElement);
Canvas.SetLeft(this, mouseWithinParent.X - _mouseLocationWithinMe.X);
Canvas.SetTop(this, mouseWithinParent.Y - _mouseLocationWithinMe.Y);
protected bool _isDragging;
Point _mouseLocationWithinMe;
这基本上是 Corey 的示例,但利用了 Hawlett 的提示。它仅在父容器是 Canvas 时才有效。此外,还应该对它进行一些限制,以防止用户将控件拖动到它真正不应该出现的位置。
【讨论】:
是的,它张贴了一些警告。它在定义的环境中对我来说很好。【参考方案6】:这段代码完美运行!
Button newBtn = new Button();
newBtn.AddHandler(Button.ClickEvent, new RoutedEventHandler(BtTable_Click));
newBtn.AddHandler(Button.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(BtTable_MouseLeftButtonDown));
newBtn.AddHandler(Button.PreviewMouseLeftButtonUpEvent, new MouseButtonEventHandler(BtTable_MouseLeftButtonUp));
newBtn.AddHandler(Button.PreviewMouseMoveEvent, new MouseEventHandler(BtTable_MouseMove));
按钮移动
private object movingObject;
private double firstXPos, firstYPos;
private int ButtonSize = 50;
private void BtTable_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
Button newBtn = sender as Button;
Canvas canvas = newBtn.Parent as Canvas;
firstXPos = e.GetPosition(newBtn).X;
firstYPos = e.GetPosition(newBtn).Y - ButtonSize;
movingObject = sender;
// Put the image currently being dragged on top of the others
int top = Canvas.GetZIndex(newBtn);
foreach (Button child in canvas.Children)
if (top < Canvas.GetZIndex(child))
top = Canvas.GetZIndex(child);
Canvas.SetZIndex(newBtn, top + 1);
Mouse.Capture(null);
private void BtTable_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
Button newBtn = sender as Button;
Canvas canvas = newBtn.Parent as Canvas;
movingObject = null;
// Put the image currently being dragged on top of the others
int top = Canvas.GetZIndex(newBtn);
foreach (Button child in canvas.Children)
if (top > Canvas.GetZIndex(child))
top = Canvas.GetZIndex(child);
Canvas.SetZIndex(newBtn, top + 1);
Mouse.Capture(newBtn);
private void BtTable_MouseMove(object sender, MouseEventArgs e)
if (e.LeftButton == MouseButtonState.Pressed && sender == movingObject)
Button newBtn = sender as Button;
Canvas canvas = newBtn.Parent as Canvas;
// Horizontal
double newLeft = e.GetPosition(canvas).X - firstXPos - canvas.Margin.Left;
// newLeft inside canvas right-border?
if (newLeft > canvas.Margin.Left + canvas.ActualWidth - newBtn.ActualWidth)
newLeft = canvas.Margin.Left + canvas.ActualWidth - newBtn.ActualWidth;
// newLeft inside canvas left-border?
else if (newLeft < canvas.Margin.Left)
newLeft = canvas.Margin.Left;
newBtn.SetValue(Canvas.LeftProperty, newLeft);
//Vertical
double newTop = e.GetPosition(canvas).Y - firstYPos - canvas.Margin.Top;
// newTop inside canvas bottom-border?
// -- Bottom --
if (newTop > canvas.Margin.Top + canvas.ActualHeight - newBtn.ActualHeight - ButtonSize)
newTop = canvas.Margin.Top + canvas.ActualHeight - newBtn.ActualHeight - ButtonSize;
// newTop inside canvas top-border?
// -- Top --
else if (newTop < canvas.Margin.Top - ButtonSize)
newTop = canvas.Margin.Top - ButtonSize;
newBtn.SetValue(Canvas.TopProperty, newTop);
编码愉快 ;)
【讨论】:
【参考方案7】:我为 WPF 和 UWP 商店应用程序实现了这一点。并将所有代码添加到用户控件本身而不是正在使用它的控件中,您可以根据需要对其进行修改。
WPF
public partial class DragUserControl : UserControl
public DragUserControl()
InitializeComponent();
object MovingObject;
double FirstXPos, FirstYPos;
private void Button_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
this.MovingObject = this;
FirstXPos = e.GetPosition(MovingObject as Control).X;
FirstYPos = e.GetPosition(MovingObject as Control).Y;
Canvas canvas = this.Parent as Canvas;
if (canvas != null)
canvas.PreviewMouseMove += this.MouseMove;
private void MouseMove(object sender, MouseEventArgs e)
/*
* In this event, at first we check the mouse left button state. If it is pressed and
* event sender object is similar with our moving object, we can move our control with
* some effects.
*/
Canvas canvas = sender as Canvas;
Point canvasPoint = e.GetPosition(canvas);
Point objPosition = e.GetPosition((MovingObject as FrameworkElement));
if (e.LeftButton == MouseButtonState.Pressed)
if (MovingObject != null)
//This condition will take care that control should not go outside the canvas.
if ((e.GetPosition((MovingObject as FrameworkElement).Parent as FrameworkElement).X - FirstXPos > 0) && (e.GetPosition((MovingObject as FrameworkElement).Parent as FrameworkElement).X - FirstXPos < canvas.ActualWidth - (MovingObject as FrameworkElement).ActualWidth))
(MovingObject as FrameworkElement).SetValue(Canvas.LeftProperty, e.GetPosition((MovingObject as FrameworkElement).Parent as FrameworkElement).X - FirstXPos);
//This condition will take care that control should not go outside the canvas.
if ((e.GetPosition((MovingObject as FrameworkElement).Parent as FrameworkElement).Y - FirstYPos > 0) && (e.GetPosition((MovingObject as FrameworkElement).Parent as FrameworkElement).Y - FirstYPos < canvas.ActualHeight - (MovingObject as FrameworkElement).ActualHeight))
(MovingObject as FrameworkElement).SetValue(Canvas.TopProperty, e.GetPosition((MovingObject as FrameworkElement).Parent as FrameworkElement).Y - FirstYPos);
private void Ellipse_PreviewMouseLeftButtonUp_1(object sender, MouseButtonEventArgs e)
MovingObject = null;
Button_MouseLeftButtonDown 是要拖动控件的按钮的点击事件。
UWP
public sealed partial class DragUserControl : UserControl
MovingObject;
double FirstXPos, FirstYPos;
public DragUserControl()
InitializeComponent();
private void Ellipse_PointerPressed(object sender, PointerRoutedEventArgs e)
this.MovingObject = this;
FirstXPos = e.GetCurrentPoint(MovingObject as Control).Position.X;
FirstYPos = e.GetCurrentPoint(MovingObject as Control).Position.Y;
Canvas canvas = this.Parent as Canvas;
if (canvas != null)
canvas.PointerMoved += Canvas_PointerMoved;
private void Canvas_PointerMoved(object sender, PointerRoutedEventArgs e)
if (MovingObject != null)
Canvas canvas = sender as Canvas;
Point canvasPoint = e.GetCurrentPoint(canvas).Position;
Point objPosition = e.GetCurrentPoint((MovingObject as FrameworkElement)).Position;
if (e.GetCurrentPoint(MovingObject as Control).Properties.IsLeftButtonPressed) //e.Pointer.IsInContact ==true)
//This condition will take care that control should not go outside the canvas
if ((e.GetCurrentPoint((MovingObject as FrameworkElement).Parent as FrameworkElement).Position.X - FirstXPos > 0) && (e.GetCurrentPoint((MovingObject as FrameworkElement).Parent as FrameworkElement).Position.X - FirstXPos < canvas.ActualWidth - (MovingObject as FrameworkElement).ActualWidth))
(MovingObject as FrameworkElement).SetValue(Canvas.LeftProperty, e.GetCurrentPoint((MovingObject as FrameworkElement).Parent as FrameworkElement).Position.X - FirstXPos);
//This condition will take care that control should not go outside the canvas
if ((e.GetCurrentPoint((MovingObject as FrameworkElement).Parent as FrameworkElement).Position.Y - FirstYPos > 0) && (e.GetCurrentPoint((MovingObject as FrameworkElement).Parent as FrameworkElement).Position.Y - FirstYPos < canvas.ActualHeight - (MovingObject as FrameworkElement).ActualHeight))
(MovingObject as FrameworkElement).SetValue(Canvas.TopProperty, e.GetCurrentPoint((MovingObject as FrameworkElement).Parent as FrameworkElement).Position.Y - FirstYPos);
private void Ellipse_PointerReleased(object sender, PointerRoutedEventArgs e)
MovingObject = null;
Ellipse_PointerPressed 是要拖动控件的椭圆的单击事件。
【讨论】:
以上是关于如何在画布内拖动用户控件的主要内容,如果未能解决你的问题,请参考以下文章