Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

My WPF application has a UserControl which 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 Window class is because it contains a third-party virtual on-screen keyboard, and that control has to be in the same window as the TextBox controls 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 TextBox controls.

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:

<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 Canvas in the window that uses it.

I can't use DragMove as it's a method of the Window class 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?

like image 511
Tony Vitabile Avatar asked Dec 13 '12 17:12

Tony Vitabile


People also ask

How do I drag and drop in WPF?

In the drag source event handler, call the DoDragDrop method to initiate the drag-and-drop operation. In the DoDragDrop call, specify the drag source, the data to be transferred, and the allowed effects. Identify the element that will be a drop target. A drop target can be UIElement or a ContentElement.

What is WPF canvas?

Canvas panel is the basic layout Panel in which the child elements can be positioned explicitly using coordinates that are relative to the Canvas any side such as left, right, top and bottom.


1 Answers

You can simply use MouseDragElementBehavior.

UPD Important thing about MouseDragElementBehavior behavior:

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.

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;
            }
        };
    }
}
like image 89
Dzmitry Martavoi Avatar answered Nov 10 '22 02:11

Dzmitry Martavoi