Tag Archives: mouse capture

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