如何在Canvas中拖动UserControl

我正在编写我的第一个WPF应用程序。 我有一个Canvas,用户可以在其中添加包含表单的UserControl子类。 用户应该能够在Canvas周围拖动这些UserControl。 使用WPF执行此操作的最佳做​​法是什么? 谢谢。

这是在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; } } 

这里有几点需要注意:
这不一定是在canvas上。 它也可以位于堆叠面板或网格中。
2.这使整个控件可拖动,这意味着如果您单击控件中的任意位置并拖动它将拖动整个控件。 不确定这究竟是你想要的。

编辑-
扩展你问题中的一些细节:我实现这个的最好方法是创建一个inheritance自UserControl的类,可能叫做使用此代码构建的DraggableControl,然后所有可拖动的控件都应该扩展DraggableControl。

编辑2 – 在此控件中有数据网格时,存在一个小问题。 如果对数据网格中的列进行排序,则MouseLeftButtonUp事件永远不会触发。 我已更新代码,以便isDragging受到保护。 我发现最好的解决方案是将此匿名方法绑定到datagrid的LostMouseCapture事件:

 this.MyDataGrid.LostMouseCapture += (sender, e) => { this.isDragging = false; }; 

关于Corey Sunwold解决方案 – 我摆脱了MouseUp和MouseDown事件,我使用MouseButtonState简化了MouseMove方法,如下所示:)我使用Canvas.SetLeft()和Canvas.SetTop()代替RenderTransform,所以我不需要存储旧的来自MouseDown事件的位置。

 if (e.LeftButton == MouseButtonState.Pressed && draggableControl != null) { //... } 

这段代码完美无缺!

 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); } } 

快乐的编码;)

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偏移,然后使用它们来增加当前运动的偏移量,以便得到正确的聚合偏移量。

对于Canvas,你可以做类似的事情(它是un VB.net,使用代码转换器进行翻译):

 Private moResumeMousePoint As New Point Private Sub Resume_PreviewMouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Dim oBorder As Border = CType(sender, Border) moResumeMousePoint = e.GetPosition(oBorder) Mouse.Capture(oBorder) End Sub Private Sub Resume_PreviewMouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Dim oBorder As Border = CType(sender, Border) oBorder.ReleaseMouseCapture() End Sub Private Sub Resume_PreviewMouseMove(sender As Object, e As MouseEventArgs) Dim oBorder As Border = CType(sender, Border) If oBorder.IsMouseCaptured Then Canvas.SetTop(oBorder, Canvas.GetTop(oBorder) + e.GetPosition(oBorder).Y - moResumeMousePoint.Y) Canvas.SetLeft(oBorder, Canvas.GetLeft(oBorder) + e.GetPosition(oBorder).X - moResumeMousePoint.X) End If End Sub 

良好的编码

我对给定的解决方案遇到了一些麻烦,最后得到了这个:

  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时起作用。 此外,它值得一些限制,以防止用户将控件拖到它真正不应该的位置。

我为WPF和UWP商店应用实现了这个function。 并且在用户控件本身而不是使用它的控件中添加了所有代码,您可以根据需要修改它。

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是椭圆的单击事件,您希望通过该事件拖动控件。