7

我的 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使用它的窗口中的 a 内。

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

4

5 回答 5

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;
            }
        };
    }
}
于 2012-12-13T18:42:35.553 回答
5

根据@DmitryMartovoi 的回答中的信息,我想出了一种方法来完成这项工作。我仍然给 Dmitry 一个 +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. 控件的父级是 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." );
    }    
}

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

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

当您单击标题栏以第二次移动它时,控件将停留在您放置它的位置,并且仅当您单击标题栏时它才会移动。单击控件中的其他任何位置都不会执行任何操作,并且拖动非常流畅且响应迅速。

于 2012-12-14T16:09:26.993 回答
2

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

于 2016-08-08T13:07:03.767 回答
0

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

我唯一的改变来自@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();
};
于 2020-04-14T18:20:58.020 回答
-1

只需执行MouseDown事件并在执行中放置this.DragMove();

例子:

<StackPanel MouseDown="UIElement_OnMouseDown">

</StackPanel>

void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e)
{
    this.DragMove();
}
于 2021-09-26T09:15:59.510 回答