I am trying to make a UserControl that drags across the canvas. I am using C# and WPF. I see many examples across the net, but I just need the bare minimum.

I found an article: "Draggable Control in WPF"

Someone responded with:

If you want to do it by hands use following algorithm:

On MouseDown event: Save Mouse position, TopLeft position of control, and delta(offset) of these coordinates, and set some boolean field flag eg. IsDragStartted to true. On MouseMove check that drag started and use Mouse position and offset to calculate the new value of TopLeft position of control

On MouseUp event set IsDragStarted to false


I am having trouble applying this.

public partial class 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;
    }



}

Here is my code for the Main Window WPF form:

{
/// <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));
        } 
    }




}

}

Here is my WPF code for the main Window:

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

I also tried placing adorners so I can eventually resize the control. that went nowhere as got the adorners but they don't do anything.

I have all my drag controls in my UserControl1 I created and I got it to drag, but when I click the UserControl1 instance again to drag it a second time it resets to the SetTop(0) and SetLeft (0) locations. It jumps there so weirdly!I was expecting the UserControl1 instance drag to the location of the cursor. It does it on the first try but then I click the UserControl1 to drag it one more time and it jumps to (0,0) or close to it.

1 Answers

0
Mark Feldman On

You've got a couple of problems here...

  1. The PreviewMouseDown event handler should be added to your child control, otherwise you won't know which object you're dragging.
  2. You're going to be dragging across the parent Canvas, so add your MouseMove handler to that.
  3. Call CaptureMouse(), again on the Canvas control, and keep it captured during the drag.
  4. Calculate the position of the mouse relative to the upper left-hand corner of the control you're dragging and then apply that offset in reverse each time you set the position. This keeps the point that you clicked on under the mouse cursor during the drag and stops it "jumping" to a new position, which is annoying to the user.

So your canvas XAML should now look like this:

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

And your code-behind should look like this:

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

You may also want to add a MouseLeave handler to the Canvas to stop the user dragging controls outside the visible client area.