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

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in z Silverlight Archives. Bookmark the permalink.