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

时间:2012-12-13 17:48:39

标签: c# wpf xaml draggable

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

我遇到的问题是拖动对话框时表现糟糕。它足够慢,鼠标离开拖动区域,它停止跟随鼠标。我需要一个更好的方法。

以下是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>

这是鼠标事件处理程序:

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

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

我无法使用DragMove,因为它是Window类的一种方法,此类来自UserControl。如何提高此控件拖动的性能?我是否必须使用Win32 API?

4 个答案:

答案 0 :(得分:6)

您只需使用MouseDragElementBehavior

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

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

您还可以实现自己的拖动行为:

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

答案 1 :(得分:4)

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

我所做的是在TranslateTransform构造函数中创建了UserControl's并将其分配给RenderTransform属性:

RenderTransform = new TranslateTransform();

在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>

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

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

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

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

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

我提出的方法实际上完美无缺,首先将控件放在Canvas中。控件的父级是Canvas或以下代码不起作用很重要。我也停止使用RenderTransform。我添加了一个名为canvas的私有属性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." );
    }    
}

完成所有这些后,这里是修改过的Mouse事件处理程序:

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

当您单击标题栏以再次移动它时,控件将保留在您放置它的位置,并且只有在您单击标题栏时它才会移动。单击控件中的任何其他位置都不会执行任何操作,并且拖动平稳且响应迅速。

答案 2 :(得分:1)

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

答案 3 :(得分:0)

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

我唯一的变化是来自@DmitryMartovoi的答案是在鼠标左键按下的地方。首次单击时,这将阻止其传送。为此,您还需要Systems.Windows.Interactivity.WPF Nuget程序包。

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