WPF C#如何使用鼠标使控件可拖动

Posted

技术标签:

【中文标题】WPF C#如何使用鼠标使控件可拖动【英文标题】:WPF C# How to Make Control Draggable with Mouse 【发布时间】:2019-09-11 06:06:44 【问题描述】:

我正在尝试制作一个在画布上拖动的 UserControl。我正在使用 C# 和 WPF。我在网上看到很多例子,但我只需要最低限度的。

找到一篇文章:《WPF中的可拖动控件》

有人回复:

如果您想手动操作,请使用以下算法:

在 MouseDown 事件中:保存鼠标位置、控件的 TopLeft 位置和这些坐标的 delta(offset),并设置一些布尔字段标志,例如。 IsDragStartted 为真。 在 MouseMove 上检查是否开始拖动并使用鼠标位置和偏移量来计算控件的 TopLeft 位置的新值

在 MouseUp 事件中将 IsDragStarted 设置为 false


我无法应用这个。

公共部分类 UserControl1:UserControl

    private Point startingMousePosition;
    private Point endingMousePosition;
    private Point startingControlPosition;
    bool isDragStarted;

    public UserControl1()
    
        InitializeComponent();

    

    private void Grid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    
        if(!isDragStarted)
        
            startingControlPosition.X = Canvas.GetLeft(this);
            startingControlPosition.Y = Canvas.GetTop(this);

            startingMousePosition.X = e.GetPosition(this.Parent as Canvas).X;
            startingMousePosition.Y = e.GetPosition(this.Parent as Canvas).Y;
        



    

    private void Grid_PreviewMouseMove(object sender, MouseEventArgs e)
    
        if (e.LeftButton == MouseButtonState.Pressed)
        
            isDragStarted = true;

            if (isDragStarted)
            

                endingMousePosition.X = e.GetPosition(this.Parent as Canvas).X;
                endingMousePosition.Y = e.GetPosition(this.Parent as Canvas).Y;

                Canvas.SetLeft(this, endingMousePosition.X - startingControlPosition.X);
                Canvas.SetTop(this, endingMousePosition.Y - startingControlPosition.Y);

            
        

    



    private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    

        isDragStarted = false;
    




这是我的主窗口 WPF 表单代码:


/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window


    UserControl1 userCTL;
    public MainWindow()
    
        InitializeComponent();

        userCTL = new UserControl1();
        userCTL.Width = 50;
        userCTL.Height = 100;
        Canvas.SetTop(userCTL,20);
        Canvas.SetLeft(userCTL, 20);

        CanvasMain.Children.Add(userCTL);

    

    private void Window_Loaded(object sender, RoutedEventArgs e)
    
        AdornerLayer myAdornerLayer = AdornerLayer.GetAdornerLayer(userCTL);
        if (myAdornerLayer != null)
        
            myAdornerLayer.Add(new SimpleCircleAdorner(userCTL));
         
    





这是我的主窗口的 WPF 代码:

<Window x:Class="WpfApplicationEvent.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="374.306" Width="594.271" Loaded="Window_Loaded">
<Grid>
    <ScrollViewer Margin="46,23,74,33" PanningMode="Both" HorizontalScrollBarVisibility="Visible">
        <Canvas x:Name="CanvasMain" Height="395" Width="506">

        </Canvas>
    </ScrollViewer>

</Grid>
</Window>

我还尝试放置装饰器,以便最终调整控件的大小。与装饰者一样无处可去,但他们什么也没做。

我在我创建的 UserControl1 中有我所有的拖动控件,我可以拖动它,但是当我再次单击 UserControl1 实例以再次拖动它时,它会重置为 SetTop(0) 和 SetLeft (0) 位置。它跳到那里太奇怪了!我期待将 UserControl1 实例拖到光标的位置。它在第一次尝试时执行,但随后我单击 UserControl1 再次拖动它,它会跳转到 (0,0) 或接近它。

【问题讨论】:

【参考方案1】:

这里有几个问题...

    PreviewMouseDown 事件处理程序应该添加到您的子控件中,否则您将不知道您正在拖动哪个对象。 您将在父 Canvas 上拖动,因此请将 MouseMove 处理程序添加到其中。 再次在 Canvas 控件上调用 CaptureMouse(),并在拖动过程中保持捕获。 计算鼠标相对于您拖动的控件左上角的位置,然后在每次设置位置时反向应用该偏移量。这会使您在拖动过程中单击鼠标光标的点保持不变,并阻止它“跳”到新位置,这对用户来说很烦人。

所以您的画布 XAML 现在应该如下所示:

<Canvas x:Name="CanvasMain" Height="395" Width="506"
    PreviewMouseMove="CanvasMain_PreviewMouseMove"
    PreviewMouseUp="CanvasMain_PreviewMouseUp" />

您的代码隐藏应该如下所示:

public MainWindow()

    InitializeComponent();

    var userCTL = new UserControl();    // <-- replace with your own control
    userCTL.Background = Brushes.Blue;  // <-- added this so I can see it
    userCTL.Width = 50;
    userCTL.Height = 100;
    Canvas.SetTop(userCTL, 20);
    Canvas.SetLeft(userCTL, 20);
    userCTL.PreviewMouseDown += UserCTL_PreviewMouseDown;

    CanvasMain.Children.Add(userCTL);


UIElement dragObject = null;
Point offset;

private void UserCTL_PreviewMouseDown(object sender, MouseButtonEventArgs e)

    this.dragObject = sender as UIElement;
    this.offset = e.GetPosition(this.CanvasMain);
    this.offset.Y -= Canvas.GetTop(this.dragObject);
    this.offset.X -= Canvas.GetLeft(this.dragObject);
    this.CanvasMain.CaptureMouse();


private void CanvasMain_PreviewMouseMove(object sender, MouseEventArgs e)

    if (this.dragObject == null)
        return;
    var position = e.GetPosition(sender as IInputElement);
    Canvas.SetTop(this.dragObject, position.Y - this.offset.Y);
    Canvas.SetLeft(this.dragObject, position.X - this.offset.X);


private void CanvasMain_PreviewMouseUp(object sender, MouseButtonEventArgs e)

    this.dragObject = null;
    this.CanvasMain.ReleaseMouseCapture();

您可能还想向 Canvas 添加一个 MouseLeave 处理程序,以阻止用户将控件拖动到可见客户区之外。

【讨论】:

非常感谢。我只有几个问题。这个 dragObject 是什么,为什么要使用它? 另外你怎么把对象转换成“FrameworkElement”? CanvasMain.CaptureMouse() 做什么以及为什么使用它?还有“ReleaseMouseCapture() 是做什么的? dragObject 只是指向您当前正在拖动的对象,它与使用布尔值基本相同。当您稍后扩展拖动功能时,保留对实际对象本身的引用会派上用场。我最初确实将其设为 FrameworkElement,但将其更改为 IInputElement,因为这是 Canvas.SetTop 等所期望的。如果需要,您可以使用 bool 或将其设为您的自定义控件类型,但通常最好的编程习惯是引用最“基”的类或接口。 捕获鼠标会导致所有鼠标事件消息被定向到捕获它的窗口,即使用户将光标移到窗口外也是如此。 ReleaseMouseCapture 只是将其恢复为正常状态,以便用户可以恢复与其他窗口等交互。尝试将这两行去掉,您会看到拖动行为有多么不同。

以上是关于WPF C#如何使用鼠标使控件可拖动的主要内容,如果未能解决你的问题,请参考以下文章

C# wpf 实现任意控件拖动

如何通过拖动扩展的窗口框架使 WPF 窗口可移动?

如何使用户控件像窗口一样在屏幕上可拖动

如何使用拇指使 WPF 弹出窗口可拖动?

如何使禁用的控件允许鼠标拖动或移动?

鼠标单击并拖动事件 WPF