December 17
Silverlight Drag and Drop and HitTest on any layout (not just Canvas)
A common requirement for a rich UI in a business application is to support dragging and dropping of UI elements. For this to be successful you will need to be able to detect what elements you are dragging over, and you will need to support dragging over any type of layout (such as a complex grid layout), which is a contrast to many examples out there that only show dragging and dropping in relation to absolute layout with a Canvas.
In addition many blogs appear to mention that the UIElement.HitTest method, available in beta’s of Silverlight 2, has since disappeared. However we have the VisualTreeHelper.FindElementsInHostCoordinates method instead that can perform a point hittest for us (that is it will return the elements a point is inside).
So to that end I have created a simple DragManager class that makes it easy to make any element draggable, and raises events when the dragged element collides with one or more elements.
To use the DragManager simply add the class to your project, create an instance of it, wire up the collision event and call EnableDragableElement on the elements you want to drag. Note that since this uses the MouseLeftButtonDown, Move and Up events, any control that handles these events internally will not work (e.g. the Button).
DragManager dm = new DragManager(LayoutRoot);
dm.Collision+= dm_Collision;
dm.EnableDragableElement(Ellipse1);
dm.EnableDragableElement(TextBlockStatus);
dm.EnableDragableElement(Image1);
void dm_Collision(object sender, CollisionEventArgs e)
{
TextBlockStatus.Text = ((FrameworkElement)e.Element).Name + " " + e.Position.X + "," + e.Position.Y + ": ";
foreach (UIElement element in e.CollidedElements)
{
TextBlockStatus.Text += ((FrameworkElement)element).Name + " ";
}
}
Of the interesting methods in the DragManager, the following performs the move for the element, and uses a TranslateTransform to move the element. This is in contrast to many examples which use the Canvas.Top and Canvas.Left properties, and has the benefit of working with any layout.
void elementToDrag_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging)
{
UIElement element = (UIElement)sender;
TranslateTransform transform = GetTranslateTransform(element);
Point currentMousePosition = e.GetPosition(layoutRoot);
double mouseX = currentMousePosition.X - lastMousePosition.X;
double mouseY = currentMousePosition.Y - lastMousePosition.Y;
transform.X += mouseX;
transform.Y += mouseY;
if (Collision != null)
{
List<UIElement> collidedElements =
VisualTreeHelper.FindElementsInHostCoordinates(
currentMousePosition, layoutRoot) as List<UIElement>;
collidedElements.Remove(element);
collidedElements.Remove(layoutRoot);
if (collidedElements.Count() > 0)
{
CollisionEventArgs args = new CollisionEventArgs()
{ Element = element,
Position = currentMousePosition,
CollidedElements = collidedElements };
Collision(this, args);
}
}
lastMousePosition = currentMousePosition;
}
}
This method calls GetTranslateTransform which looks for an existing TranslateTransform on the element or adds one if not present.
It also calls the VisualTreeHelper.FindElementsInHostCoordinates method to determine if a collision has taken place and returns a collection of the elements that have been hit (note that we need to remove the layout root and the element we are dragging since they will always be returned.)
Full source is available here:
Cheers
Ian