Dragging shapes with the mouse in WPF

In the last post we saw what happens when the mouse is used to click on a Button. The mouse is, of course, often used  to select a particular point on the interface. A common feature is that of dragging an object from one location to another (often as part of a drag and drop operation, but that’s a bit more advanced so we’ll leave that for a future post). Here we have a look at a simple application in which the mouse can be used to drag two shapes around.

Although most of the action happens in the event handlers, we can start the project as usual in Expression Blend (EB). After the project is created, the first thing we need to do is to change the layout type. To do this right-click on LayoutRoot (the default Grid layout that is provided for you when you create a project) and select “Change Layout Type”. Change the layout to a Canvas. A Canvas layout is one in which all elements must be placed manually, that is, (x,y) coordinates must be given for each component. Although this sort of layout might seem easier than having to cope with the vagaries of a Grid, it lacks any flexibility so that if the window is resized, for example, components will appear badly aligned or, if the window is made smaller, they could become cropped or disappear altogether. However, for images and drawings, the Canvas is usually the best layout to use.

To finish the setup, add an ellipse and a circle to the Canvas. It doesn’t matter where you put them since the aim of this program is to allow them to be moved around. However, it’s a good idea if they don’t overlap in the initial setup. In our example, we placed the square at location (10,10) (that is, 10 units to the left and down from the top-left corner), and the ellipse at (200,100). We also made the square red and the ellipse blue. The initial layout looks like this:

We would like to be able to click the left button of the mouse over one of the shapes and then drag that shape around the Canvas, with the shape remaining in the new position when the button is released. We therefore need to handle 3 events: MouseLeftButtonDown, MouseMove and MouseLeftButtonUp. We will write these event handlers in such a way that the same event handlers can be used for both shapes. In EB, use the Events panel for the ellipse to add handlers for these 3 events. Give them obvious names, like shape_MouseLeftButtonDown, etc. Then select the Rectangle and give the same 3 events the same handlers as those you gave the ellipse.

If you’ve set things up properly, Visual Studio (VS) will open to let you edit the handler code in the C# file that lies behind the XAML for the main window. Here’s the code for the first bit of the class, including MouseLeftButtonDown:

  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
    }

    bool captured = false;
    double x_shape, x_canvas, y_shape, y_canvas;
    UIElement source = null;

    private void shape_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
      source = (UIElement)sender;
      Mouse.Capture(source);
      captured = true;
      x_shape = Canvas.GetLeft(source);
      x_canvas = e.GetPosition(LayoutRoot).X;
      y_shape = Canvas.GetTop(source);
      y_canvas = e.GetPosition(LayoutRoot).Y;
    }

On lines 8 to 10, we declare a few variables that we’ll use throughout the class, and we’ll explain them as we go along.

First, we want to make the handlers generic so they can be used with both shapes. The source object is declared as a UIElement, which is the base class of all user-interface elements, and is what the Canvas functions expect as arguments. When the left mouse button is pressed, calling the handler that starts on line 12, it will provide the object that triggered the event as the sender argument in the handler. On line 14, we cast this into a UIElement.

On line 15, we capture the mouse, binding it to the shape over which it was clicked. This means that all mouse events will relate to that shape, even if the mouse should be moved outside the Canvas. We’ll see later what difference this makes.

Next, we set a boolean flag captured to true, indicating that the mouse is captured, and that the left button has been pressed.

The remainder of the code, on lines 17 through 20, records the (x,y) coordinates of source (the shape) relative to LayoutRoot (the Canvas) in doubles (x_shape, y_shape). It also records the position of the mouse relative to LayoutRoot, storing this in (x_canvas, y_canvas). Note that the mouse position is obtained by calling GetPosition() from the MouseButtonEventArgs parameter passed to the hander.

We’ve now captured the mouse with the selected shape and recorded the position of the shape and the mouse when the left button was pressed. In the MouseMove handler, we do the moving of the shape.

    private void shape_MouseMove(object sender, MouseEventArgs e)
    {
      if (captured)
      {
        double x = e.GetPosition(LayoutRoot).X;
        double y = e.GetPosition(LayoutRoot).Y;
        x_shape += x - x_canvas;
        Canvas.SetLeft(source, x_shape);
        x_canvas = x;
        y_shape += y - y_canvas;
        Canvas.SetTop(source, y_shape);
        y_canvas = y;
      }
    }

We want the shape to move only if the left mouse button is down, so we check the captured flag on line 3. Next, we obtain the current position of the mouse on lines 5 and 6. Then we calculate what the new coordinates of the shape should be and use the static functions in Canvas to set the new position. We also update x_canvas and y_canvas so they can be used to calculate how much to move the shape the next time the MouseMove handler is called. The MouseMove handler is called only every so often as the mouse is moved, so you won’t get a call every time a coordinate value changes (unless you move the mouse very slowly), but unless you move the mouse very quickly, the motion will appear smooth.

Finally, we need the handler for releasing the mouse button. This is quite simple:

    private void shape_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
      Mouse.Capture(null);
      captured = false;
    }

We have to ‘uncapture’ the mouse which we do by calling Capture() with a null argument, and we turn off the captured flag. That’s it for the code.

Finally, we promised we’d explain what difference capturing the mouse would make. This is easy enough to test; we merely comment out the call to Mouse.Capture(source) on line 15 in the MouseLeftButtonDown code above. The program appears to run properly, but note if you drag a shape and let the mouse go outside the Canvas, the shape will stop moving as soon as the mouse leaves the Canvas. If you put the call to Capture(source) back in, you’ll see the shape continue to move even after the mouse has left the Canvas. This latter behaviour is because the shape has sole use of the mouse when it captures it, so mouse move events are still routed back to the handler for that shape. If the shape did not capture the shape, then mouse events are routed to whatever is outside the Canvas when the mouse leaves the Canvas, and the shape stops moving.

Complete project files for this example are here.

Advertisements
Post a comment or leave a trackback: Trackback URL.

Comments

  • abvw91  On April 15, 2013 at 9:34 AM

    GREAT THANKS MAN ! Awesome for an newbie 😉 .
    I’d like to ask you, in my case i create shapes using code behind, so i haven’t xaml code of these shapes, how can i attribute events to my shapes ? THANK YOU :).

  • abvw91  On April 15, 2013 at 10:16 AM

    OH,

    I find a way to do that, after trying something simple 😀 !!!

    //NEW SHAPE
    Ellipse place = new Ellipse();

    // SET EVENTS TO CREATED SHAPE
    place.MouseLeftButtonDown += shape_MouseLeftButtonDown;
    place.MouseMove += shape_MouseMove;
    place.MouseLeftButtonUp += shape_MouseLeftButtonUp;

    THANK YOU !

    • Anonymous  On October 2, 2015 at 5:46 AM

      using System;
      using System.Collections.Generic;
      using System.Diagnostics;
      using System.Linq;
      using System.Text;
      using System.Windows;
      using System.Windows.Controls;
      using System.Windows.Data;
      using System.Windows.Documents;
      using System.Windows.Input;
      using System.Windows.Media;
      using System.Windows.Media.Imaging;
      using System.Windows.Navigation;
      using System.Windows.Shapes;

      namespace DragShapes
      {
      ///
      /// Interaction logic for MainWindow.xaml
      ///
      ///
      public partial class MainWindow : Window
      {
      Ellipse place = new Ellipse();
      public MainWindow()
      {
      place.MouseLeftButtonDown += shape_MouseLeftButtonDown;
      place.MouseMove += shape_MouseMove;
      place.MouseLeftButtonUp += shape_MouseLeftButtonUp;
      InitializeComponent();

      }

      public enum ShapeMode
      {
      LINE,
      FILLELIPSE,
      DRAWELIPSE,
      FILLRECTANGLE,
      DRAWRECTANGLE,
      }
      private ShapeMode _curShapeMode;

      public ShapeMode CurShapeMode
      {
      get { return _curShapeMode; }
      set { _curShapeMode = value; }
      }
      bool captured = false;
      double x_shape, x_canvas, y_shape, y_canvas;
      UIElement source = null;

      private void shape_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
      {
      source = (UIElement)sender;
      Mouse.Capture(source);
      captured = true;
      x_shape = Canvas.GetLeft(source);
      x_canvas = e.GetPosition(LayoutRoot).X;
      y_shape = Canvas.GetTop(source);
      y_canvas = e.GetPosition(LayoutRoot).Y;
      }

      private void shape_MouseMove(object sender, MouseEventArgs e)
      {
      if (captured)
      {
      double x = e.GetPosition(LayoutRoot).X;
      double y = e.GetPosition(LayoutRoot).Y;
      x_shape += x – x_canvas;
      Canvas.SetLeft(source, x_shape);
      x_canvas = x;
      y_shape += y – y_canvas;
      Canvas.SetTop(source, y_shape);
      y_canvas = y;
      }
      }

      private void shape_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
      {
      Mouse.Capture(null);
      captured = false;
      switch (_curShapeMode)
      {
      case ShapeMode.DRAWRECTANGLE:

      break;
      case ShapeMode.DRAWELIPSE:
      // Ellipse place = new Ellipse();
      place.Width = x_shape;
      place.Height = y_shape;
      SolidColorBrush sb = new SolidColorBrush();
      sb.Color = Colors.BurlyWood;
      place.StrokeThickness = 4;
      place.Stroke = sb;
      LayoutRoot.Children.Add(place);
      break;
      case ShapeMode.LINE:

      break;

      }
      }

      private void btnElipse_Click(object sender, RoutedEventArgs e)
      {
      _curShapeMode = ShapeMode.DRAWELIPSE;
      }
      }
      }
      I can’t move drag mouse to draw a elipse. Help me

  • Felipe  On October 3, 2013 at 5:51 AM

    Awesome work ! Thanks for this material.
    I’m newbie with WPF … and I was wondering how can I save the UI Element position after been draged and load my app with the new position. I still don’t get how do save the position in the XAML. Can you point me a direction ? I really apreciate any help. Thanks.

  • z  On November 17, 2013 at 9:27 AM

    thanks for that,

    also, what whould be the best way to follow when instead of moving the shapes around and freely placeing them wherever one has this window with many panels, user controls,… on which it is only possible to place the dragged UIElement in a specifc user control’s area (for example)?

  • aldy syahdeini  On December 28, 2013 at 9:49 AM

    hey where should I put this event, in my shape or where?
    when I put it in window,canvas or shape it doesn’t work

  • Anonymous  On January 25, 2014 at 11:48 PM

    thanks man 😀

  • TeaDrivenDev  On October 19, 2014 at 3:51 AM

    Simple and straightforward solution; easy to port to F# for use from F# Interactive.

  • sleepLessLonelyhood  On December 15, 2014 at 11:25 PM

    Thanks!

  • bchappe  On April 27, 2015 at 3:01 PM

    Hello ! Simple and effective exemple but does not work when you did not explicitly set the Canvas.top and Left property on your shape because in this case both

    x_shape = Canvas.GetLeft(source);

    y_shape = Canvas.GetTop(source);

    return NaN 😉

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: