C# 如何在画布内拖动用户控件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1495408/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to drag a UserControl inside a Canvas
提问by loris
I have a Canvas in which user can add UserControl subclasses containing a form. User should be able to drag these UserControl around the Canvas.
我有一个 Canvas,用户可以在其中添加包含表单的 UserControl 子类。用户应该能够在画布周围拖动这些 UserControl。
What's the best practice to do this with WPF?
使用 WPF 执行此操作的最佳做法是什么?
采纳答案by Corey Sunwold
This is done in silverlight and not in WPF, but it should work the same.
这是在 Silverlight 中完成的,而不是在 WPF 中完成的,但它的工作方式应该相同。
Create two private properties on the control:
在控件上创建两个私有属性:
protected bool isDragging;
private Point clickPosition;
Then attatch some event handlers in the constructor of the control:
然后在控件的构造函数中附加一些事件处理程序:
this.MouseLeftButtonDown += new MouseButtonEventHandler(Control_MouseLeftButtonDown);
this.MouseLeftButtonUp += new MouseButtonEventHandler(Control_MouseLeftButtonUp);
this.MouseMove += new MouseEventHandler(Control_MouseMove);
Now create those methods:
现在创建这些方法:
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;
}
}
A few things to note here:
1. This does not have to be in a canvas. It can be in a stackpanel, or grid as well.
2. This makes the entire control draggable, that means if you click anywhere in the control and drag it will drag the whole control. Not sure if thats exactly what you want.
这里有几点需要注意:
1. 这不必在画布中。它也可以在堆栈面板或网格中。
2. 这使得整个控件可拖动,这意味着如果您单击控件中的任意位置并拖动它,则会拖动整个控件。不确定这是否正是您想要的。
Edit-
Expanding on some of the specifics in your question:
The best way that I would implement this is to create a class that inherits from UserControl, maybe called DraggableControl that is built with this code, then all draggable controls should extend the DraggableControl.
编辑 -
扩展您的问题中的一些细节:我实现这一点的最佳方法是创建一个继承自 UserControl 的类,可能称为使用此代码构建的 DraggableControl,然后所有可拖动控件都应扩展 DraggableControl。
Edit 2 - There is small issue when you have a datagrid in this control. If you sort a column in the datagrid the MouseLeftButtonUp event never fires. I have updated the code so that isDragging is protected. I found the best solution is to tie this anonymous method to the LostMouseCapture event of the datagrid:
编辑 2 - 当您在此控件中有数据网格时会出现一个小问题。如果您对数据网格中的列进行排序,则永远不会触发 MouseLeftButtonUp 事件。我已经更新了代码,以便保护 isDragging。我发现最好的解决方案是将此匿名方法与数据网格的 LostMouseCapture 事件联系起来:
this.MyDataGrid.LostMouseCapture += (sender, e) => { this.isDragging = false; };
回答by Hawlett
Regarding Corey Sunwoldsolution - I got rid of MouseUp and MouseDown events and I simplified MouseMove method using MouseButtonStateas below :) I'm using Canvas.SetLeft() and Canvas.SetTop() instead RenderTransform so I don't need to store old position from MouseDown event.
关于Corey Sunwold解决方案 - 我摆脱了 MouseUp 和 MouseDown 事件,并使用MouseButtonState简化了 MouseMove 方法,如下所示:) 我使用 Canvas.SetLeft() 和 Canvas.SetTop() 代替 RenderTransform 所以我不需要存储旧的MouseDown 事件中的位置。
if (e.LeftButton == MouseButtonState.Pressed && draggableControl != null)
{
//...
}
回答by Wally Hynds
I had some trouble with the given solutions and ended up with this:
我在给定的解决方案上遇到了一些麻烦,最终得到了这个:
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;
}
It is basically Corey's example but leverages hints from Hawlett. It works ONLY when the parent container is a Canvas. Also, it deserves to be dolled up with some limits to keep the user from dragging the control to places it really should not be.
它基本上是 Corey 的例子,但利用了 Hawlett 的提示。它仅在父容器是 Canvas 时有效。此外,它应该有一些限制,以防止用户将控件拖到它真正不应该出现的地方。
回答by Rahul Sonone
I implemented this for both WPF and UWP store app. And added all the code in user control itself instead of the control which is using it, you can modify it as par your need.
我为 WPF 和 UWP 商店应用程序实现了这个。并在用户控件本身而不是使用它的控件中添加了所有代码,您可以根据需要对其进行修改。
WPF
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 is the click event of button through which you want to drag the control.
Button_MouseLeftButtonDown 是要拖动控件的按钮的单击事件。
UWP
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 is the click event of ellipse through which you want to drag the control.
Ellipse_PointerPressed 是要拖动控件的椭圆的单击事件。
回答by Rahul Sonone
This code works perfectly!
这段代码完美运行!
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));
Button Move
按钮移动
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);
}
}
Happy coding ;)
快乐编码;)
回答by DonBoitnott
Corey's answer is mostly correct, but it's missing one crucial element: memory of what the last transform was. Otherwise, when you move an item, release the mouse button, and then click that item again, the transform resets to (0,0)
and the control jumps back to its origin.
科里的回答大部分是正确的,但它缺少一个关键要素:对上次变换是什么的记忆。否则,当您移动一个项目时,松开鼠标按钮,然后再次单击该项目,变换将重置为(0,0)
并且控件跳回其原点。
Here's a slightly modified version that works for me:
这是一个对我有用的稍微修改过的版本:
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;
}
}
}
}
The key is storing the previous X and Y offsets, and then using them to augment the current movement's offset in order to arrive at the correct aggregate offset.
关键是存储之前的 X 和 Y 偏移量,然后使用它们来增加当前移动的偏移量,以达到正确的聚合偏移量。
回答by Themelis
If someone wants to quickly see this effect, here is a minimal solutionusing only MouseMove
event.
如果有人想快速看到这种效果,这里有一个仅使用事件的最小解决方案MouseMove
。
The Layout
布局
<Canvas Background='Beige'
Name='canvas'>
<Rectangle Width='50'
Height='50'
Fill='LightPink'
Canvas.Left='350'
Canvas.Top='175'
MouseMove='Rectangle_MouseMove' />
</Canvas>
Code behind
背后的代码
private 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();
}
}
}