Page view counter

Drag and Drop with Managed Code

 

The last time I looked at Drag and Drop was with Silverlight version 1 and now seemed like a good time to see how one might accomplish this seemingly straight forward task with managed code.

The experience was interesting. Once I had it working, it was hard to explain why it had been difficult, though it did seem that the documentation took you right up to what you needed to know and then stopped short. I'm not certain that is true, but I know I struggled longer than I expected to.  In any case, now that it is working, it is easy to demonstrate.

This blog entry will make short work of it. To keep things simple, we'll just draw a circle and a square and drag them around the surface of the larger canvas (we'll use canvas because it uses absolute positioning which makes positioning the shape much easier). I've added color to the canvas so you can see where its boundaries are.

DragAndDropRunning
(Composite image of dragging circle)

  
The Xaml file is very simple,

<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="DragDrop.Page"
Width="800"
Height="600"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<Canvas Background="AntiqueWhite">
<Ellipse
x:Name="Circle"
Height="150"
Width="150"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Fill="#FFFFFF00"
Stroke="#FF000000"
Canvas.Left="10"
Canvas.Top="25" />

<Rectangle
x:Name="Square"
Height="100"
Width="100"
Canvas.Left="200"
Canvas.Top="50"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Fill="Red"
Stroke="Black"
StrokeThickness="1"
Opacity="0.75" />
</Canvas>
</UserControl>

The Supporting Code

All of the code for this example is in Page.xaml.cs.  We want to implement three event handlers for each shape:

  1. MouseLeftButtonDown – the user has started to drag
  2. MouseMove – the user is dragging
  3. MouseLeftButtonUp – the user has stopped dragging

The key to understanding how this works is that these events are fired by the shapes themselves, not by the mouse or the canvas. Once that is clear, the rest follows naturally.

To make for easier-to-maintain code, we'll share event handlers between the Square and the Circle.

public partial class Page : UserControl
{




// are we currently tracking the mouse?
private bool isTracking = false;
  // constructor
public Page()
{
InitializeComponent();

Circle.MouseLeftButtonDown +=
new MouseButtonEventHandler( Shape_MouseLeftButtonDown );
Square.MouseLeftButtonDown +=
new MouseButtonEventHandler( Shape_MouseLeftButtonDown );

Circle.MouseLeftButtonUp +=
new MouseButtonEventHandler( Shape_MouseLeftButtonUp );
Square.MouseLeftButtonUp +=
new MouseButtonEventHandler( Shape_MouseLeftButtonUp );

Circle.MouseMove +=
new MouseEventHandler( Shape_MouseMove );
Square.MouseMove +=
new MouseEventHandler( Shape_MouseMove );
}

 

MouseLeftButtonDown

Clicking the left mouse button acts as a signal to begin dragging. We'll set the member flag to indicate that we are tracking, and then we'll want to set a few properties on the shape.

Unfortunately, we have only the sender (type object) and the MouseButtonEventArgs OriginalSource property (also of type object). This is not a problem, however, as we know that any object firing this event is of type shape, so we are safe to cast accordingly,

void Shape_MouseLeftButtonDown( 
object sender,
MouseButtonEventArgs e )
{
isTracking = true;
Shape s = e.OriginalSource as Shape;


// For robust code, you should test that s is not null

With a reference to the shape, you can now set the opacity to half its current value. We won't set it to a fixed value (e.g., 0.5) as that would break any shape whose initial opacity is not 1 (such as our square!).

s.Opacity *= 0.5;

(We'll restore this later by doubling the value, returning it to whatever it was, without the need to cache the original value)

When the user is dragging, it is possible that the mouse will move off the shape which will cause the MouseMove event to stop firing (the technical term for this is "bad"). To prevent that, we'll capture the mouse:

s.CaptureMouse();

MouseMove

Each time the user moves the mouse a MouseMove event is fired. We test for whether we are tracking the mouse (the IsTracking private member variable is set in ButtonDown and cleared in ButtonUp) and if so we get the current position of the mouse from the MouseEventArgs,

void Shape_MouseMove( object sender, MouseEventArgs e )
{
if ( isTracking )
{
double currentMouseX = e.GetPosition( null ).X;
double currentMouseY = e.GetPosition( null ).Y;

These values will be the new position for the Left and Top values of our shape. While this works okay for a square, it is counter intuitive for a Circle as a Circle's top-left corner is actually that of the invisible bounding rectangle, as illustrated here (with the bounding rectangle filled in artificially in turquoise),

BoundingRect1

Since the bounding rectangle is actually invisible however, the user is forced to drag from a point to the north-west of the circle, a disconcerting and odd experience,

BoundingRect2

To eliminate that, we'll reposition to the center of the shape,

    Shape s = e.OriginalSource as Shape;
    Canvas.SetLeft( s, currentMouseX  - s.Width / 2);
    Canvas.SetTop( s, currentMouseY - s.Height / 2);

MouseLeftButtonUp

When the left mouse button is released, we're done dragging, we can release our capture of the mouse and set IsTracking to false, remembering to restore the opacity to its original value

void Shape_MouseLeftButtonUp( 
object sender,
MouseButtonEventArgs e )
{
isTracking = false;
Shape s = e.OriginalSource as Shape;
s.Opacity *= 2;
s.ReleaseMouseCapture();
}

 

Here is the complete Page.xaml.cs in one place for easy copying:

using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Shapes;

namespace DragDrop
{
public partial class Page : UserControl
{
private bool isTracking = false;

public Page()
{
InitializeComponent();
Circle.MouseLeftButtonDown +=
new MouseButtonEventHandler( Shape_MouseLeftButtonDown );
Square.MouseLeftButtonDown +=
new MouseButtonEventHandler( Shape_MouseLeftButtonDown );

Circle.MouseLeftButtonUp +=
new MouseButtonEventHandler( Shape_MouseLeftButtonUp );
Square.MouseLeftButtonUp +=
new MouseButtonEventHandler( Shape_MouseLeftButtonUp );

Circle.MouseMove +=
new MouseEventHandler( Shape_MouseMove );
Square.MouseMove +=
new MouseEventHandler( Shape_MouseMove );
}

// ** Button down **
void Shape_MouseLeftButtonDown(
object sender,
MouseButtonEventArgs e )
{
isTracking = true;
Shape s = e.OriginalSource as Shape;
s.Opacity *= 0.5;
s.CaptureMouse();
}


// ** Mouse Move **
void Shape_MouseMove(
object sender,
MouseEventArgs e )
{
if ( isTracking )
{
// get the mouse position
double currentMouseX = e.GetPosition( null ).X;
double currentMouseY = e.GetPosition( null ).Y;

// center the shape at the mouse position
Shape s = e.OriginalSource as Shape;
Canvas.SetLeft( s, currentMouseX - s.Width / 2);
Canvas.SetTop( s, currentMouseY - s.Height / 2);
}
}

// ** Button up **
void Shape_MouseLeftButtonUp(
object sender,
MouseButtonEventArgs e )
{
isTracking = false;
Shape s = e.OriginalSource as Shape;
s.Opacity *= 2;
s.ReleaseMouseCapture();
}
} // end class
} // end namespace
 
Try it: 

 

 

-j

Published 13 January 2009 09:46 AM by jesseliberty

Comments

# daf555 said on 13 January, 2009 12:44 PM

Hi there!

It would nice if there is a clickable demo inside this blog entry.

Greets, Daniel.

# esh said on 13 January, 2009 02:08 PM

Both clickable demo and source code would be nice for your blog entries.

Thanks for lots of helpful information.

# Community Blogs said on 13 January, 2009 02:58 PM

\ In this issue: Boyan Mihaylov, Thiago Felix, Bart Czernicki, Daomon Payne, Nigel Sampson, Jeff Weber

# IanBlackburn said on 13 January, 2009 03:01 PM

Hi Jesse,

This is a nice example, but is limited in that it requires the use of a Canvas.

I put together an example over on my Silverlight for Buiness blog (silverlightforbusiness.net) that shows how to perform drag and drop over any layout and also how to do a basic hit test:

blackburnian.spaces.live.com/.../cns!FB8B852EF1AB0B35!1759.entry

Hope someone finds it useful!

Cheers

Ian

# Steve Strong said on 13 January, 2009 04:05 PM

I have done a lot of drag and drop in silverlight,  I have found the a better technique is to move the Mouse down and Mouse UP events from the actual objects, and place these events on the canvas,  Then use the visual tree to method do the hit test...

           Point oPoint = e.GetPosition(null);

           KnowledgeShape oFound = null;

           foreach (UIElement oElement in VisualTreeHelper.FindElementsInHostCoordinates(oPoint, this))

           {

               KnowledgeShape oShape = oElement as KnowledgeShape;

               if (SelectStatus(oShape) == SelectionStatus.OnShape)

                   oFound = oShape;

               else if ( oFound == null && SelectStatus(oShape) == SelectionStatus.OnPage)

                   oFound = oShape;

           }

# jesseliberty said on 14 January, 2009 09:30 AM

Responding to comments:  It is always a balancing act as to how much to include in a blog entry.  I agree that a working model would have been good; I'll add that now. As for source, I did include the complete source, so I'm not sure why adding downloadable source would be helpful; I'm willing but in this case you can copy and paste.

Steve, I like what you've done, but the goal here was just to show the fundamentals. Yours is very nice, but not quite as straightforward (imho).

In any case, I'll update right now with a working version in an iframe.

# jesseliberty said on 14 January, 2009 10:24 AM

That sounds way too  defensive to my ears. Time for me to put together a FAQ about (a) how I approach this blog and (b) where to get more info and advanced coverage. Steve and Ian, thanks for the pointers on more advanced coverage.

-j

# bobdupuy said on 14 January, 2009 12:42 PM

Setting the event handlers (mouse down, move, up) directly on the graphic objects is very easy to implement, as the hit testing is done by the framework.

But it has a drawback: Sometimes, you need to do an action (drag and drop for example) when the mouse is close to the object, but still not inside it. For example, try to drag and drop a line that's 1 pixel wide...You probably want to add some extra space around the line to make interaction easier...

This is possible when using the FindElementsInHostCoordinates method that takes a rectangle as parameter.

Robert.

# thebirdbath said on 14 January, 2009 01:48 PM

Found a bug.

Grab the circle and then roll over to the square.  It will automatically grab the square and then fade the circle out a bit in opacity.....keep doing it and the circle will disappear entirely.  

I'm a blend/xaml designer so I can't give you any pointers on how to fix that, all I can help you with is making a much prettier square and circle. :)

-zac

# Boot2TheHead said on 14 January, 2009 03:53 PM

I reproduced the snapover bug that thebirdbath found. I also managed to get the circle to stay 50% opaque (not sure how) and move the objects off the canvas. So it'll need some work but it's a helpful example.

# Drag & Drop mit Managed Code at Programming with Silverlight, WPF & .NET said on 15 January, 2009 04:34 AM

Pingback from  Drag &#038; Drop mit Managed Code at Programming with Silverlight, WPF &amp; .NET

# Boot2TheHead said on 15 January, 2009 04:48 PM

Weird, that snapover bug only happens when the circle goes over the square but not vice versa.

# noamarbel said on 16 January, 2009 06:35 PM

Of course, its the order that events get fired. The squere gets the event first (as it was registered last).

# IanBlackburn said on 17 January, 2009 11:29 AM

The url to my blog entry above seems to have been comprehensively mangled some how, so here is a tinyurl instead:

http://tinyurl.com/8zt98x

Cheers

# ABL said on 03 February, 2009 11:38 AM

The scrumwall application at www.scrumwall.com/scrumwall.aspx has a good amount of drag and drop. Looks cool!

Requires registering for the demo though.

Search

Go

This Blog

News

     
     
    What's New In Silverlight 3
    A Frequently Updated WikiDoc .
    Last Update: July 10
     
    AgOpenSource
    From Design to implementation
     
    Better Videos
    Diary of an experiment in extreme post-production
     
    Quick Bits
    Too big for Twitter, Too Small for the blog
     
    New to Silverlight?
    Click here to get started.

Syndication