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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13865517/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 06:36:57  来源:igfitidea点击:

How to make a user control draggable on screen like a window

c#wpfxamldraggable

提问by Tony Vitabile

My WPF application has a UserControlwhich is supposed to look and behave like a popup window, but it isn't a window. The reason the control doesn't descend from the Windowclass is because it contains a third-party virtual on-screen keyboard, and that control has to be in the same window as the TextBoxcontrols that it sends input characters to when you click on its buttons. If the keyboard control is not in the same window, it can't even see the TextBoxcontrols.

我的 WPF 应用程序的UserControl外观和行为应该像一个弹出窗口,但它不是一个窗口。控件不从Window类继承的原因是因为它包含第三方虚拟屏幕键盘,并且该控件必须与TextBox单击其按钮时将输入字符发送到的控件位于同一窗口中. 如果键盘控件不在同一个窗口中,它甚至看不到TextBox控件。

The problem I'm having is performance is abysmal when dragging the dialog around. It's sufficiently slow that the mouse comes off the drag area and it stops following the mouse. I need a better way.

我遇到的问题是拖动对话框时性能非常糟糕。鼠标离开拖动区域并停止跟随鼠标的速度足够慢。我需要更好的方法。

Here's an excerpt from the xaml for the control:

这是控件的 xaml 的摘录:

<Grid Name="LayoutRoot">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Border Background="{DynamicResource PopupBackground}"
            BorderBrush="{DynamicResource PopupBorder}"
            BorderThickness="5,5,5,0"
            MouseLeftButtonDown="Grid_MouseLeftButtonDown"
            MouseLeftButtonUp="Grid_MouseLeftButtonUp"
            MouseMove="Grid_MouseMove">
    . . .
    </Border>
</Grid>

Here's the mouse event handlers:

这是鼠标事件处理程序:

    private void Grid_MouseLeftButtonDown( object sender, MouseButtonEventArgs e ) {
        Canvas canvas = Parent as Canvas;
        if ( canvas == null ) {
            throw new InvalidCastException( "The parent of a KeyboardPopup control must be a Canvas." );
        }
        DraggingControl = true;
        CurrentMousePosition = e.GetPosition( canvas );
        e.Handled = true;
    }

    private void Grid_MouseLeftButtonUp( object sender, MouseButtonEventArgs e ) {
        Canvas canvas = Parent as Canvas;
        if ( canvas == null ) {
            throw new InvalidCastException( "The parent of a KeyboardPopup control must be a Canvas." );
        }

        if ( DraggingControl ) {
            Point mousePosition = e.GetPosition( canvas );

            // Correct the mouse coordinates in case they go off the edges of the control
            if ( mousePosition.X < 0.0 ) mousePosition.X = 0.0; else if ( mousePosition.X > canvas.ActualWidth ) mousePosition.X = canvas.ActualWidth;
            if ( mousePosition.Y < 0.0 ) mousePosition.Y = 0.0; else if ( mousePosition.Y > canvas.ActualHeight ) mousePosition.Y = canvas.ActualHeight;

            // Compute the new Left & Top coordinates of the control
            Canvas.SetLeft( this, Left += mousePosition.X - CurrentMousePosition.X );
            Canvas.SetTop( this, Top += mousePosition.Y - CurrentMousePosition.Y );
        }
        e.Handled = true;
    }

    private void Grid_MouseMove( object sender, MouseEventArgs e ) {
        Canvas canvas = Parent as Canvas;
        if ( canvas == null ) {
            // It is not.  Throw an exception
            throw new InvalidCastException( "The parent of a KeyboardPopup control must be a Canvas." );
        }

        if ( DraggingControl && e.LeftButton == MouseButtonState.Pressed ) {
            Point mousePosition = e.GetPosition( canvas );

            // Correct the mouse coordinates in case they go off the edges of the control
            if ( mousePosition.X < 0.0 ) mousePosition.X = 0.0; else if ( mousePosition.X > canvas.ActualWidth  ) mousePosition.X = canvas.ActualWidth;
            if ( mousePosition.Y < 0.0 ) mousePosition.Y = 0.0; else if ( mousePosition.Y > canvas.ActualHeight ) mousePosition.Y = canvas.ActualHeight;

            // Compute the new Left & Top coordinates of the control
            Canvas.SetLeft( this, Left += mousePosition.X - CurrentMousePosition.X );
            Canvas.SetTop ( this, Top  += mousePosition.Y - CurrentMousePosition.Y );

            CurrentMousePosition = mousePosition;
        }
        e.Handled = true;
    }

Note that the control must be placed inside a Canvasin the window that uses it.

请注意,该控件必须放置在Canvas使用它的窗口中的a 内。

I can't use DragMoveas it's a method of the Windowclass and this class descends from UserControl. How do I improve the performance of this control's dragging? Do I have to resort to Win32 APIs?

我不能使用,DragMove因为它是Window类的方法,而这个类是从UserControl. 如何提高此控件拖动的性能?我是否必须求助于 Win32 API?

采纳答案by Tony Vitabile

Based upon information in @DmitryMartovoi's answer, I have come up with a way to make this work. I'm still giving Dmitry a +1 as I wouldn't have been able to figure this out without his contribution.

根据@DmitryMartovoi 的回答中的信息,我想出了一种方法来完成这项工作。我仍然给 Dmitry 一个 +1,因为如果没有他的贡献,我将无法弄清楚这一点。

What I did was I created a TranslateTransformin my UserControl'sconstructor and assigned it to its RenderTransformproperty:

我所做的是TranslateTransformUserControl's构造函数中创建了一个并将其分配给它的RenderTransform属性:

RenderTransform = new TranslateTransform();

In the XAML, I named the Bordercontrol that the user clicks on to drag the whole control:

在 XAML 中,我为Border用户单击以拖动整个控件的控件命名:

<Border Background="{DynamicResource PopupBackground}"
        BorderBrush="{DynamicResource PopupBorder}"
        BorderThickness="5,5,5,0"
        MouseLeftButtonDown="Grid_MouseLeftButtonDown"
        MouseLeftButtonUp="Grid_MouseLeftButtonUp"
        MouseMove="Grid_MouseMove"
        Name="TitleBorder">

    . . .
</Border>

Finally, I modified the various Mouse event handlers as follows:

最后,我修改了各种鼠标事件处理程序,如下所示:

private void Grid_MouseLeftButtonDown( object sender, MouseButtonEventArgs e ) {
    CurrentMousePosition = e.GetPosition( Parent as Window );
    TitleBorder.CaptureMouse();
}

private void Grid_MouseLeftButtonUp( object sender, MouseButtonEventArgs e ) {
    if ( TitleBorder.IsMouseCaptured ) {
        TitleBorder.ReleaseMouseCapture();
    }
}

private void Grid_MouseMove( object sender, MouseEventArgs e ) {
    Vector diff = e.GetPosition( Parent as Window ) - CurrentMousePosition;
    if ( TitleBorder.IsMouseCaptured ) {
        ( RenderTransform as TranslateTransform ).X = diff.X;
        ( RenderTransform as TranslateTransform ).Y = diff.Y;
    }
}

This works beautifully. The entire UserControland all of its contents move smoothly when you drag the Border, keeping up with the mouse. And the entire UserControldoes not move if you click anywhere else on its surface.

这很好用。UserControl当您拖动 时,整个及其所有内容都会平滑移动Border,并与鼠标保持同步。UserControl如果您单击其表面上的任何其他位置,则整体不会移动。

Thanks again to @DmitryMartovoi for the code he supplied.

再次感谢@DmitryMartovoi 提供的代码。

EDIT: I am editing this answer because the above code, while it worked, wasn't perfect. Its flaw is that the control would pop back to its original location on screen when you clicked on the title bar area and before you started dragging. This was annoying and totally wrong.

编辑:我正在编辑这个答案,因为上面的代码虽然有效,但并不完美。它的缺陷是当您单击标题栏区域和开始拖动之前,控件会弹回到屏幕上的原始位置。这很烦人,而且完全错误。

The approach I came up with that actually worked flawlessly involved first putting the control in a Canvas. It's important that the parent of the control be a Canvasor the following code won't work. I also stopped using the RenderTransform. I added a private property called canvasof type Canvas. I added a Loadedevent handler to the popup control to do some important initialization:

我想出的实际上完美无缺的方法涉及首先将控件放入Canvas. 控件的父级为 a 很重要,Canvas否则以下代码将不起作用。我也停止使用RenderTransform. 我添加了一个名为canvastype的私有属性Canvas。我向Loaded弹出控件添加了一个事件处理程序来进行一些重要的初始化:

private void KeyboardPopup_Loaded( object sender, RoutedEventArgs e ) {
    canvas = Parent as Canvas;
    if ( canvas == null ) {
        throw new InvalidCastException( "The parent of a KeyboardPopup control must be a Canvas." );
    }    
}

With all of this done, here are the modified Mouse event handlers:

完成所有这些后,以下是修改后的鼠标事件处理程序:

private void TitleBorder_MouseLeftButtonDown( object sender, MouseButtonEventArgs e ) {
    StartMousePosition = e.GetPosition( canvas );
    TitleBorder.CaptureMouse();
}

private void TitleBorder_MouseLeftButtonUp( object sender, MouseButtonEventArgs e ) {
    if ( TitleBorder.IsMouseCaptured ) {
        Point mousePosition = e.GetPosition( canvas );
        Canvas.SetLeft( this, Canvas.GetLeft( this ) + mousePosition.X - StartMousePosition.X );
        Canvas.SetTop ( this, Canvas.GetTop ( this ) + mousePosition.Y - StartMousePosition.Y );
        canvas.ReleaseMouseCapture();
    }
}

private void TitleBorder_MouseMove( object sender, MouseEventArgs e ) {
    if ( TitleBorder.IsMouseCaptured && e.LeftButton == MouseButtonState.Pressed ) {
        Point mousePosition = e.GetPosition( canvas );

        // Compute the new Left & Top coordinates of the control
        Canvas.SetLeft( this, Canvas.GetLeft( this ) + mousePosition.X - StartMousePosition.X );
        Canvas.SetTop ( this, Canvas.GetTop ( this ) + mousePosition.Y - StartMousePosition.Y );
        StartMousePosition = mousePosition;
    }
}

The control stays where you dropped it when you click on the title bar to move it a second time, and it only moves when you click on the title bar. Clicking anywhere else in the control does nothing, and dragging is smooth and responsive.

当您再次单击标题栏以移动控件时,控件会停留在您放置它的位置,并且仅在您单击标题栏时才会移动。单击控件中的任何其他位置什么都不做,拖动是平滑且响应迅速的。

回答by Dzmitry Martavoi

You can simply use MouseDragElementBehavior.

您可以简单地使用MouseDragElementBehavior

UPDImportant thing about MouseDragElementBehaviorbehavior:

UPD关于MouseDragElementBehavior行为的重要事项:

The MouseDragElementBehavior behavior doesn't work for any controls that handle MouseClick events (Button, TextBox, and ListBox controls, for example). If you need the ability to drag a control of one of these types, make that control a child of a control that can be dragged (a border, for example). You can then apply the MouseDragElementBehavior behavior to the parent element.

MouseDragElementBehavior 行为不适用于处理 MouseClick 事件的任何控件(例如,Button、TextBox 和 ListBox 控件)。如果您需要能够拖动其中一种类型的控件,请将该控件设为可拖动控件的子控件(例如边框)。然后,您可以将 MouseDragElementBehavior 行为应用到父元素。

You can also implement your own drag behavior like this:

您还可以实现自己的拖动行为,如下所示:

public class DragBehavior : Behavior<UIElement>
{
    private Point elementStartPosition;
    private Point mouseStartPosition;
    private TranslateTransform transform = new TranslateTransform();

    protected override void OnAttached()
    {
        Window parent = Application.Current.MainWindow;
        AssociatedObject.RenderTransform = transform;

        AssociatedObject.MouseLeftButtonDown += (sender, e) => 
        {
            elementStartPosition = AssociatedObject.TranslatePoint( new Point(), parent );
            mouseStartPosition = e.GetPosition(parent);
            AssociatedObject.CaptureMouse();
        };

        AssociatedObject.MouseLeftButtonUp += (sender, e) =>
        {
            AssociatedObject.ReleaseMouseCapture();
        };

        AssociatedObject.MouseMove += (sender, e) =>
        {
            Vector diff = e.GetPosition( parent ) - mouseStartPosition;
            if (AssociatedObject.IsMouseCaptured)
            {
                transform.X = diff.X;
                transform.Y = diff.Y;
            }
        };
    }
}

回答by Rahul Sonone

http://www.codeproject.com/Tips/442276/Drag-and-Drop-WPF-ControlsThis is the awesome solution I got after spending lot of time. Although example shown here are normal controls but after some changes you can make it work for user controls also.

http://www.codeproject.com/Tips/442276/Drag-and-Drop-WPF-Controls这是我花了很多时间后得到的很棒的解决方案。尽管此处显示的示例是普通控件,但经过一些更改后,您也可以使其适用于用户控件。

回答by TheRoyalMidget

My solution for this is a mix between @DmitryMartovoi's and this thread: https://www.codeproject.com/Questions/1014138/Csharp-WPF-RenderTransform-resets-on-mousedown

我对此的解决方案是@DmitryMartovoi 和此线程之间的混合:https://www.codeproject.com/Questions/1014138/Csharp-WPF-RenderTransform-resets-on-mousedown

My only change was from @DmitryMartovoi's answer is in the left button mouse down. This stops it from teleporting when you first click it. To do this you will also need the Systems.Windows.Interactivity.WPFNuget package.

我唯一的变化来自@DmitryMartovoi 的答案是在鼠标左键按下。当您第一次单击它时,这会阻止它传送。为此,您还需要Systems.Windows.Interactivity.WPFNuget 包。

AssociatedObject.MouseLeftButtonDown += (sender, e) =>
{
    var mousePos = e.GetPosition(parent);
    mouseStartPosition = new Point(mousePos.X-transform.X, mousePos.Y-transform.Y);
    AssociatedObject.CaptureMouse();
};