Moving objects around interactively

I was playing around with Blend today, and decided that I wanted to write a function to allow my user to drag the shapes around on my artboard. Granted, this could use a bit of polish, since there are some tweaks that would make it a bit better, but the overall sense should work.

For starters, we need to add a couple private variables to our Window... so I added these three lines:

private Point initialDragPoint = new Point(0, 0);

private Point currentDragPoint = new Point(0, 0);

private bool dragging = false;

My naming may not be the best, but what I'm setting up here is to keep track of where I clicked the mouse on my object, and for any mouse event, where I am now. I'm also setting up a boolean flag, just to make the code a bit cleaner.

Now, I've already opened Blend, and created a Rectangle. Also, since I don't really want/need all of the layout power of the Grid, I've changed the layout type of my root element to a Canvas. You COULD do this whole thing with RenderTransforms, but in this case, I think that's just more hassle then it's worth.

We need to hook up three events... MouseDown, MouseMove and MouseUp. We'll start with MouseDown...

private void StartMove(object sender, System.Windows.Input.MouseButtonEventArgs e)

{

      if (!this.dragging)

      {

            this.initialDragPoint = e.GetPosition(this);

            this.dragging = true;

      }

}

Ok, this method won't do a whole heck of a lot, but what it does is remember the point where the mouse came down, then swap the flag to remember that we're now moving. You'll want to hook this event up to the "MouseDown" event on your shape(s) that you want to allow the user to move.

Next, we'll do MouseUp (which is when the user releases the mouse button).

private void EndMove(object sender, System.Windows.Input.MouseEventArgs e)

{

      if (this.dragging)

      {

            this.dragging = false;

      }

}

Again, another simple one... all we do is tell our system that we're done dragging when the user lets go of the mouse button. Finally, the workhorse... MouseMove.

private void Moving(object sender, System.Windows.Input.MouseEventArgs e)

{

      if (this.dragging)

      {

            this.currentDragPoint = e.GetPosition(this);

            Vector dragVector = this.currentDragPoint - this.initialDragPoint;

            FrameworkElement actor = sender as FrameworkElement;

            double curLeft = (double)actor.GetValue(Canvas.LeftProperty);

            double curTop = (double)actor.GetValue(Canvas.TopProperty);

            curLeft += dragVector.X;

            curTop += dragVector.Y;

            actor.SetValue(Canvas.LeftProperty, curLeft);

            actor.SetValue(Canvas.TopProperty, curTop);

            this.initialDragPoint = this.currentDragPoint;

      }

}

Let's look at it piece by piece.

First, obviously, we only want to do something if we're currently dragging. So, we gate the whole method to only act if this.dragging is true.

Next, we figure out where the mouse currently is. e.GetPosition(this) does that for me. For the record, the 'this' as an argument just says that I want the point relative to the window. Since I used (this) in MouseDown as well, both of my points will be in the same scale.

To see how this works, let's say that when we did the MouseDown, we were 100 pixels down and 100 pixels to the right of the top left of our window. Then, when we got the MouseUp, we were 120 pixels down and 120 pixels to the right. So, this.initialDragPoint would be (100,100). The e.GetPosition(this) sets the currentDragPoint to (120,120).

The next step is to calculate our dragging Vector. We want our rectangle to move down and to the right, so the calculation is easy... Now, I could have used a Point just as easily, but I tend to think of this as an actual movement vector, so I used the Vector class instead. dragVector gets set to (120-100, 120-100) or (20,20), which is the movement we want on our rectangle.

Next, I need to get the actor (or the shape that my user is moving), and I need it as a FrameworkElement. By casting it to a Framework element, this same code would work for anything... a Grid with a bunch of elements in it... a single rectangle... pretty much anything.

Then, I just use GetValue to get the current Left and Top (see why I wanted to use a Canvas), adjust them by the dragVector, and then set those values.

Finally, the last thing I need to do, is set THIS point as my new base point, so that the new drag is relative to here. Imagine if I hadn't reset it, and then moved the mouse 20 more pixels to the right. The initial point would be 100,100, but the new dragPoint would be (140,120), so my rectangle, which I already moved 20x20 pixels, would be moved another 40x20 pixels. That's no good. So, updating that base value is important.

Granted, there are still some quirks here... for instance, you could start the drag with the left mouse button, then press the right mouse button while dragging... then releasing EITHER button would drop it. I'm sure there are plenty of refinements... but the point is that this should give everyone a good starting point.

Comments

  • Anonymous
    April 08, 2008
    Dante, Can you give your thoughts on how one would implement snapping a dragged object to some reference point. For example some programs snap to a grid or snap to an edge of an adjacent object. That would be useful for many scenarios I can think of.

  • Anonymous
    April 08, 2008
    The comment has been removed

  • Anonymous
    August 13, 2008
    I have a user control, which contains a few grids.  Inside one of those grids is a stackpanel, which, when loaded, dynamically adds a few more custom controls. I want to be able to drag these custom controls around on the whole page.. I do not have a canvas object, so actor.SetValue(Canvas.TopProperty) etc do nothing... How do move these objects around in reference to the entire layout?