Introduction
This is a continuation of a tutorial, see Silverlight 2.0 In Examples: Part 0. Introduction” and “Silverlight 2.0 In Examples: Part 1. Silverlight Elements: Panels and Controls.
Since recently I’ve been working on Silverlight Drag and Drop, I decided to break the continuity of this tutorial and skip several parts, writing right away about how to implement Drag and Drop in Silverlight. In addition to information from the previous sections of the tutorial, this part requires some knowledge of DataTemplates and binding (I will give brief explanations of both).
The article that started me on Silverlight 2.0 Drag and Drop can be accessed via the following link: Drag and Drop in Silverlight. At the bottom of the article you can find a link to download the source code.
Here, however, I go over Drag and Drop with more details and examples. A special feature of this article is a Drag and Drop custom control designed to absorb most of the Drag and Drop complexity. Based on this control, I created an example functionally similar to the one described in Drag and Drop in Silverlight.
Silverlight demos, including those corresponding to the samples presented here can be found at AWebPros Demos.
Simple Silverlight Drag and Drop Example
Here is the screen capture of the first example:
One can drag and drop the red circle anywhere within the Silverlight application area.
The source code for this sample can be downloaded from simpledragdropzip.doc. As always, please remove .doc extension, rename the file to simpledragdrop.zip and then open it as a zip file.
Below the code of the sample is explained in detail.
Here are the contents of Page.xaml file:
<UserControl
x:Class=”SimpleDragDrop.Page”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
Background=”Yellow”
Width=”400″
Height=”300″>
<UserControl.Resources>
<DataTemplate x:Key=”Circle”>
<Ellipse Width=”20″ Height=”20″ Fill=”Red”></Ellipse>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name=”LayoutRoot” Background=”White”>
<Popup x:Name=”MyPopup”IsOpen=”False”>
<ContentControl
ContentTemplate=”{StaticResource Circle}”
Opacity=”0.5″/>
</Popup>
<ContentControl
ContentTemplate=”{StaticResource Circle}”
x:Name=”MyControlToMove”>
</ContentControl>
</Grid>
</UserControl>
The object that we are dragging and dropping is ContentControl at the very bottom of the XAML file. Its name is “MyControlToMove”.
ContentControl is a control that has two major properties: Content (which contains some data) and ContentTemplate (which specifies how to present this data).
In our case the presentation of MyControlToMove is not data dependent (Content property does not play any role). This presentation is determined only by the DataTemplate which is defined as a resource called “Circle” within UserControl.Resources section. In this example this ContentControl is always displayed as a red circle.
Another control is used as a drag cursor to show where the circle is dragged. It is represented by a similar red circle with its opacity property set to 0.5, making it semitransparent. This control is of Popup type.
Now, let us deconstruct the C# code. While XAML contains mostly design and static information, C# carries information about actions. There are 3 main actions within the Drag and Drop process:
Correspondingly, we have 3 event handlers for 3 events:
All of the events are registered with the top level panel “LayoutRoot”. This is possible because of the event “Bubbling”. Even if a child of “LayoutRoot” panel is clicked, the event eventually “bubbles” up to it, unless it is handled before.
Here is how we set the event handlers within the application:
LayoutRoot.MouseLeftButtonDown +=
new MouseButtonEventHandler(LayoutRoot_MouseLeftButtonDown);
LayoutRoot.MouseMove +=
new MouseEventHandler(LayoutRoot_MouseMove);
LayoutRoot.MouseLeftButtonUp +=
new MouseButtonEventHandler(LayoutRoot_MouseLeftButtonUp);
Now let us describe these 3 operations in detail.
Starting the Drag Operation
Starting the drag operation is handled by the function LayoutRoot_MouseLeftButtonDown which in turn calls StartDragDrop.
Here is the code for LayoutRoot_MouseLeftButtonDown function:
void
LayoutRoot_MouseLeftButtonDown(object sender,
MouseButtonEventArgs e)
{
// obtain all UI elements at the
// current mouse pointer location
List<UIElement> elements =
(List<UIElement>)this.HitTest(e.GetPosition(null));
// get the first element of type Ellipse and
// start drag operation on it.
foreach (UIElement element in elements)
{
if (element is Ellipse)
{
StartDragDrop(element, e);
break;
}
}
}
First HitTest gets all the elements “pierced” by the current mouse pointer. Then we iterate over each of of the elements, find the “Ellipse” (this is our circle we want to move) and start drag operation on it by calling StartDragDrop function.
Here is the StartDragDrop function code:
private void
StartDragDrop(UIElement element,
MouseButtonEventArgs e)
{
// make the popup visible
MyPopup.IsOpen = true;
// make the mouse events connected to
// the popup
MyPopup.CaptureMouse();
// figure out the mouse coordinates at the onset
// of Drag operation.
_horisontalOffset = e.GetPosition(null).X;
_verticalOffset = e.GetPosition(null).Y;
// set _captured flag to true
// (this is to check later if drag drop
// operation is in progress)
_captured = true;
// move the popup to the current mouse location.
MyPopup.HorizontalOffset = _horisontalOffset;
MyPopup.VerticalOffset = _verticalOffset;
}
The StartDragDrop functionality is explained in the comments within its code.
Drag Operation During the Mouse Move
Here is how we implemented Drag operation when mouse moves:
void
LayoutRoot_MouseMove(object sender,
MouseEventArgs e)
{
// if no drag and drop started,
// we do not need to do anything.
if (!_captured)
return;
// update the popup location to the
// current mouse pointer location.
MyPopup.HorizontalOffset = e.GetPosition(null).X;
MyPopup.VerticalOffset = e.GetPosition(null).Y;
}
All we need to do is to move the Drag popup to the current location of the mouse pointer.
Drop Operation
Finally, here is the function in charge of Drop operation:
void
LayoutRoot_MouseLeftButtonUp(object sender,
MouseButtonEventArgs e)
{
// if drag is not started we do not need
// any drop functionality to execute
if (!_captured)
return;
// mouse capture is released
MyPopup.ReleaseMouseCapture();
// popup becomes invisible
MyPopup.IsOpen = false;
// set captured flag to false
// (no drag operation in progress)
_captured = false;
// the rest of the code just moves
// our content control to the new location
// using TranslateTransform
GeneralTransform gt =
this.TransformToVisual(MyControlToMove);
PointstartPoint = gt.Transform(new Point(0, 0));
Pointp = e.GetPosition(null);
TranslateTransformtt = new TranslateTransform();
tt.X = p.X – _horisontalOffset – startPoint.X;
tt.Y = p.Y – _verticalOffset – startPoint.Y;
MyControlToMove.RenderTransform = tt;
}
As you can see, we stop the DragDrop operation by releasing the mouse capture, making the Drag popup invisible and setting _captured flag to false. Then we do the actual drop operation by moving the original dragged control to the new location using TranslateTransform.
Drag and Drop with Forbidden Area
Now, let us consider a more complicated example.
In addition to the previously discussed functionality it has the following:
Here is the screen capture for the example:
The yellow circle signifies the area where the drop is allowed. Outside of it, the drop is forbidden.
Here is the code for the example: dragdropwithforbiddenareazip.doc.
As one can see from the code, the capture process now begins during MouseMove action and only when the distance between the original and current locations of the mouse is greater than _captureRadius:
private void BeginCapture(MouseEventArgs e)
{
// check if the mouse pointer position
// within _captureRadius
double diffX =
e.GetPosition(null).X – _horisontalOffset;
double diffY =
e.GetPosition(null).Y – _verticalOffset;
// if it is within _captureRadion,
// do not start capture
if ((diffX * diffX + diffY * diffY) <
_captureRaduis * _captureRaduis)
{
return;
}
// make popup visible
MyPopup.IsOpen = true;
// capture mouse
MyPopup.CaptureMouse();
// set _captured flag to true.
_captured = true;
}
There is also a function InAllowedArea that returns true if the mouse pointer is within an area in which we are allowed to drop and false otherwise:
// checks if drop is allowed
// at the current location of the
// mouse pointer.
bool InAllowedArea(MouseEventArgs e)
{
Point p = e.GetPosition(null);
IEnumerable<UIElement> hitTestResult =
AllowedArea.HitTest(p);
// see if mouse pointer hits AllowedArea
// if yes, return true,
// if no, return false.
return(hitTestResult.Count() != 0);
}
Then, during the Drag process, we check if we are within the area in which we are allowed to drop and set the ContentTemplate of the PopupControl correspondingly:
if(InAllowedArea(e))
{
// if we are in an area
// in which we are
// allowed to drop
// set the template accordingly
// to our Circle
PopupControl.ContentTemplate = CircleTemplate;
}
else
{
// if we are in an area in which it
// is forbidden to drop, set
PopupControl.ContentTemplate = DropForbidden;
}
Generic Drag and Drop Control
Based on the above examples and also taking into account that the Drag popup display can be data driven (as will be shown later) I came up with a generic Drag/Drop control implementation. While it is still rather complex to use, since drag and drop is a complex operation, it also absorbs a lot of complexity into itself, eliminating the need to implement and debug a large chunk of the Drag/Drop functionality over and over again.
Here is the code for generic Drag/Drop control together with a couple of samples showing how to use it: genericdragdropzip.doc.
DragDrop control is defined within GenericDragDropLib project within DragDropControl.cs file. The generic.xaml file contains default control template for DragDropControl.
DragDropControl contains a number of properties and events that allow customization of the Drag/Drop functionality. Here is the list of these properties and events with the explanations as to why they are needed.
// mouse is captured and the popup
// indicating the beginning of Drag
// operation to the user becomes
// visible on when the distance between
// the mouse pointer and the original
// point when the mouse button was pressed
// during the Drag operation is
// greater than CaptureRadius.
public doubleCaptureRadius { get; set; }
// the popup to show during Drag operation
public Popup DragDropPopup { get; set; }
// The content control within the DragDropPopup
// that can assume any shape defined by
// its data template and date content.
public ContentControl PopupContentControl { get; set; }
// default data template for areas in
// which drop is allowed
public DataTemplate DropAllowedTemplate { get; set; }
// data template for areas in which
// drop is forbidden
public DataTemplate DropForbiddenTemplate { get; set; }
// element to be dragged
public UIElementDraggedElement { get; set; }
// dragged business logic object
// (specified by Content property of DraggedElement)
public objectDraggedObject { get; set; }
// DragContainer property is used when DraggedElement is
// one of the items within and ItemsControl e.g. a
// ListBox this property contains the reference to this
// container control of the dragged item
public UIElementDragContainer {get; set;}
// does the drop operation
public event DoDropDelegateDoDrop = null;
// used to filter in the element that can be
// dragged
public event IsDraggableDelegateIsDraggable = null;
// used to filter in the container of the element
// that can be dragged
public event IsContainerDelegate IsDragContainer = null;
// returns true if, during the drag operation,
// the mouse pointer is inside the area
// it which drops are allowed, false otherwise.
public event IsInAllowedAreaDelegateIsInAllowedArea = null;
// returns data template for the case when the drops are
// allowed during the drag operation
public event GetDataTemplateDelegate GetDataTemplate = null;
//used to set the DraggedObject from the
// DraggedElement.
// Most of the times, this is just DraggedElement.Content
public event GetBusinessLogicObjectDelegate
GetBusinessLogicObject = null;
// used to record the origin of the drag operation
public Point StartDragPoint { get; set; }
DragDropControl also contains three functions that need to be triggered by the Button Down, Mouse Move and Button Up events of the application that uses DragDropControl. These functions are
Below we describe two examples using DragDropControl.
Drag Drop with Forbidden Area Implemented Using DragDropControl
The solution for this test is called GenericDragDropTest.sln and it is located under GenericDragDropTest within genericdragdrop.zip file. It produces exactly the same result as our Drag/Drop with forbidden area example above, but it uses DragDropControl. Below we show how DragDropControl is used.
Here is the code showing how we connect the events of DragDropControl:
MyDragDropControl.DoDrop +=
new GenericDragDropLib.
DoDropDelegate(MyDragDropControl_DoDrop);
// we only drag ellipses!
MyDragDropControl.IsDraggable +=
delegate(UIElement element)
{
if (element is Ellipse)
return true;
return false;
};
// data template for the drag popup is
// provided by CircleTemplate resource
MyDragDropControl.GetDataTemplate +=
delegate(UIElement element)
{
return (DataTemplate) this.Resources[“CircleTemplate”];
};
// we are in an area in which are
// are allowed to drop if the
// mouse pointer is
// inside AllowedArea circle.
MyDragDropControl.IsInAllowedArea +=
delegate(Point p)
{
IEnumerable<UIElement> hitTestResult =
AllowedArea.HitTest(p);
return(hitTestResult.Count() != 0);
};
And here is the code that connects the mouse button events to the DragDropControl functions:
// connect the mouse button events to
// the corresponding functions
// of our DragDropControl.
LayoutRoot.MouseLeftButtonDown +=
delegate(object sender, MouseButtonEventArgs e)
{
MyDragDropControl.StartDragDrop(this, e);
};
LayoutRoot.MouseMove +=
delegate(object sender, MouseEventArgs e)
{
MyDragDropControl.OnMove(e);
};
LayoutRoot.MouseLeftButtonUp +=
delegate(object sender, MouseButtonEventArgs e)
{
MyDragDropControl.OnButtonUp(e);
};
Drag and Drop between Two ListBox Elements
Here we show an implementation of Drag/Drop analogous to that of Drag and Drop in Silverlight. The ListBoxItem objects are dragged and dropped between two ListBoxes.
The code for this sample can be found under ListBoxDragDropTest directory of genericdragdrop.zip file.
Here is the screen capture for this sample:
This sample also provides an example of having data driven visual presentation of the dragged item (the first and last names of the students provide the data that can change depending on the dragged item).
Here is the code showing setting the event handlers for DragDropControl events:
MyDragDropControl.DoDrop +=
new GenericDragDropLib.DoDropDelegate(DoDrop);
// we only Drag and Drop ListBoxItem objects!
MyDragDropControl.IsDraggable +=
delegate(UIElement element)
{
if (element is ListBoxItem)
return true;
return false;
};
// our drag container is ListBox
MyDragDropControl.IsDragContainer +=
delegate(UIElement element)
{
if (element is ListBox)
return true;
return false;
};
// we use the same DataTemplate
// to display the drag popup as
// we use to display the items in the list
MyDragDropControl.GetDataTemplate +=
delegate(UIElement element)
{
ListBoxItemlbi = MyDragDropControl.DraggedElement as ListBoxItem;
if(lbi == null)
return null;
return (DataTemplate)lbi.ContentTemplate;
};
// we are in allowed area if and only if we are
// inside a ListBox
MyDragDropControl.IsInAllowedArea +=
delegate(Point p)
{
ListBoxlb = GetListBox(p);
if (lb == null)
return false;
return true;
};
// returns Content property of the ListBoxItem
MyDragDropControl.GetBusinessLogicObject +=
delegate(UIElement element)
{
ContentControl cc = element as ContentControl;
if (cc == null)
return null;
return cc.Content;
};
One can see that the DoDrop function (called during the drop operation) checks if the target ListBox is not the same as the original ListBox from where the item was dragged and if true, removes the item from the original collection and adds it to the collection of the target ListBox.
Conclusion
This article describes the implementation of drag and drop functionality in Silverlight. It starts with a simple example and progresses to more complex ones. It features custom DragDropControl which is used to absorb a lot of complexity of Drag/Drop implementation.
Tags: Drag and Drop, Silverlight
December 4, 2008 at 5:32 am |
Small update required for Silverlight 2:
DragDropControl.cs – Line: 132 – Replace [TopLevelDragContainer.HitTest(startPoint);] with [VisualTreeHelper.FindElementsInHostCoordinates(startPoint, TopLevelDragContainer);]
ListBoxDragDropTest – Page.xaml.cs – Line: 97 – Replace [hitTestResults = LayoutRoot.HitTest(p);] with [VisualTreeHelper.FindElementsInHostCoordinates(p, LayoutRoot);]
December 9, 2008 at 7:58 pm |
Failed at DragDropPopup.IsOpen .
This is null;
December 9, 2008 at 8:33 pm |
Sorry, can you be more specific. What were you trying to do when the failure occured?
December 29, 2008 at 4:09 am |
Failed at DragDropPopup.IsOpen while trying to click on the object. Object is null.
January 4, 2009 at 9:03 pm |
I got the same error when attempting to initiate a drag operation. It’s because the the template was not being applied so your DragDropPopup was never being initialized (since OnApplyTemplate was never called) and that’s what leads to the null reference exception on the DragDropPopup.IsOpen line.
The solution is to move the generic.xaml file that’s contained in the GenericDragDropLib project into a new sub-folder called Themes as required by the final version of SL.
January 5, 2009 at 2:46 am |
Sorry, i have not had time to update my blog recently.
Thanks for figuring it out!
March 5, 2009 at 9:58 pm |
Very nice job. Well commented and the article was very clear. Thanks, it taught me a lot. I’m new to Silverlight and did run into something I just can’t figure out. I’m using the GenericDragDropLib for copying items from one list box to others. I have a control template for my list items that basically makes the items magnify when the mouse moves over them (CommonStates MouseOver). When an item gets dragged the source (original) item does not shrink down when the mouse leaves with the dragging copy. It’s like the item does not change states when the mouse is no longer over the item. I have to run the mouse over it again after the drop and it restores.
Any ideas?
March 6, 2009 at 3:43 am |
Hey Jim,
apparently for some reason MouseLeave event does not fire. You can probably add the shrinking functionality to DoDrop function.
If you send me your code, I’ll be happy to try to figure it out.
March 6, 2009 at 4:05 am |
Actually, re-reading your comment more carefully, apparently you are using the VSM to expand and shrink each item. VisualStateManager can be controlled programmatically.
Basically you can get the VisualStateGroups from the visual state manager and then states from the groups and then you can get a needed storyboard from the state. Then, you can probably run the story board whenever you need by using Begin method.
Examples of getting the visual states programmatically can be found at http://silverlight.net/forums/p/17802/60387.aspx.
Again I’ll be able to say more if I have the code.
March 29, 2009 at 11:23 pm |
Hi,
I’ve tested your code, very interesting indeed…
I had to make some minor modifications in the code though.
As Rob Gibson said, the function HitTest is no longer supported by the Silverlight 2 RTM version (and the Silverlight 3 Beta also…)
so we have to use VisualTreeHelper class instead.
Example: In the SimpleDragAndDrop application the following line:
void LayoutRoot_MouseLeftButton(object sender, MouseButtonEventArgs e)
{
List element = (List)this.HitTest(e.GetPosition(null)); ….
will generate an error with Silverlight 2 RTM version
and should therefore be replace with:
void LayoutRoot_MouseLeftButton(object sender, MouseButtonEventArgs e)
{
IEnumerable elements = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(null), this);
in order to run properly.
I’ve also made the same type of modifications in the ListDragAndDrop application so that
the code no longer generates any errors during compilation. However when I run
the ListDragAndDrop application, the elements in the listbox “Box1” just won’t respond to any behaviour… Strange…
I’m sure that any update of your code would be very much appreciated.
Thank you and keep on your ggod work.
March 31, 2009 at 6:26 am |
Hi MavashiKid,
thanks for your remarks. I am a little out of date on my blog matters because I am working on a big Silverlight project.
Can you send an ftp or http url to download your code that does not work. I can try to figure out what is wrong.
Concerning the code update – I’ll try to post it within a week or two.
May 11, 2009 at 2:53 pm |
리거니의 생각…
Silverlight 2.0 In Examples: Part ? Drag and Drop Inside Out « Nick Polyak’s Software Blog(고급)…
September 3, 2009 at 2:02 pm |
Great man,
Thanks!
You are de first who used the events on the Grid “LayoutRoot”. I see many peoples who used the events on the objects and not maked the test on the function
” foreach (UIElement element in elements)
{
if (element is Ellipse)”
I was looking for this. I will make an example based on your’s post in my blog(Portuguese).
October 18, 2011 at 6:39 am |
programming…
[…]Silverlight 2.0 In Examples: Part ? Drag and Drop Inside Out « Nick Polyak’s Software Blog[…]…
January 28, 2013 at 8:02 pm |
certainly like your web site but you need to check the spelling on several
of your posts. Many of them are rife with spelling problems and I in finding it very troublesome to tell the reality however I will certainly
come again again.
March 15, 2013 at 9:45 pm |
Sorry about this, Alexis, Hopefully the content will compensate for some spelling errors:)
January 7, 2014 at 1:18 pm |
excellent submit, very informative. I wonder why the
other specialists of this sector do not understand this.
You should continue your writing. I am confident, you’ve
a huge readers’ base already!