Cómo arrastrar un UserControl dentro de un Canvas

Estoy escribiendo mi primera aplicación WPF. Tengo un canvas en el que el usuario puede agregar subclases UserControl que contienen un formulario. El usuario debería poder arrastrar estos UserControl alrededor del canvas. ¿Cuál es la mejor práctica para hacer esto con WPF? Gracias.

Esto se hace en Silverlight y no en WPF, pero debería funcionar igual.

Crea dos propiedades privadas en el control:

protected bool isDragging; private Point clickPosition; 

A continuación, agregue algunos controladores de eventos en el constructor del control:

 this.MouseLeftButtonDown += new MouseButtonEventHandler(Control_MouseLeftButtonDown); this.MouseLeftButtonUp += new MouseButtonEventHandler(Control_MouseLeftButtonUp); this.MouseMove += new MouseEventHandler(Control_MouseMove); 

Ahora crea esos métodos:

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

Algunas cosas para tener en cuenta aquí:
1. Esto no tiene que estar en un canvas. Puede estar en un panel de distribución, o cuadrícula también.
2. Esto hace que todo el control sea arrastrable, lo que significa que si haces clic en cualquier parte del control y arrastras, arrastrará todo el control. No estoy seguro si eso es exactamente lo que quieres.

Editar-
Ampliando algunos de los detalles en su pregunta: La mejor forma en que implementaría esto es crear una clase que herede de UserControl, tal vez llamada DraggableControl que está construida con este código, luego todos los controles arrastrables deberían extender el DraggableControl.

Editar 2: hay un pequeño problema cuando tienes una cuadrícula de datos en este control. Si ordena una columna en la cuadrícula de datos, el evento MouseLeftButtonUp nunca se dispara. He actualizado el código para que isDragging esté protegido. Encontré que la mejor solución es unir este método anónimo al evento LostMouseCapture de la cuadrícula de datos:

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

En cuanto a la solución de Corey Sunwold , eliminé los eventos MouseUp y MouseDown y simplifiqué el método MouseMove con MouseButtonState como el siguiente 🙂 Estoy usando Canvas.SetLeft () y Canvas.SetTop () en vez de RenderTransform, así que no necesito almacenar viejos posición del evento MouseDown.

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

Este código funciona perfectamente!

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

Botón mover

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

Feliz encoding;)

La respuesta de Corey es en su mayoría correcta, pero le falta un elemento crucial: memoria de lo que fue la última transformación. De lo contrario, cuando mueva un elemento, suelte el botón del mouse y vuelva a hacer clic en ese elemento, la transformación se restablece a (0,0) y el control vuelve a su origen.

Aquí hay una versión ligeramente modificada que funciona para mí:

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

La clave es almacenar los desplazamientos X e Y anteriores, y luego usarlos para boost el desplazamiento del movimiento actual para llegar al desplazamiento de agregado correcto.

Tuve algunos problemas con las soluciones dadas y terminé con esto:

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

Básicamente es el ejemplo de Corey, pero aprovecha los consejos de Hawlett. Funciona ÚNICAMENTE cuando el contenedor principal es un canvas. Además, merece ser embellecido con algunos límites para evitar que el usuario arrastre el control a lugares que realmente no debería ser.

Implementé esto para la aplicación de tienda WPF y UWP. Y agregó todo el código en el control del usuario en lugar del control que lo está usando, puede modificarlo según su necesidad.

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 es el evento click del botón a través del cual desea arrastrar el control.

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 es el evento click de elipse a través del cual desea arrastrar el control.