I wrote some classes for handling drag and drop operations in Silverlight that I use within Perenthia. I thought I would go ahead and share them since I found them to be very useful in getting drag and drag working quickly in new projects.
The Drag and Drag classes consist of two interfaces (IDroppable, IDropContainer) and a manager class (DragDropManager). The interfaces are defined as such:
namespace Lionsguard
{
/// <summary>
/// Defines an object that can be dragged and dropped via the DragDropManager.
/// </summary>
public interface IDroppable
{
/// <summary>
/// An event that is raised when the IDroppable item begins the drag operation.
/// </summary>
event BeginDragEventHandler BeginDrag;
/// <summary>
/// Gets the UIElement that will server as the cursor replacement when dragging the current object.
/// </summary>
/// <returns></returns>
UIElement GetDragCursor();
}
/// <summary>
/// Defines an object that accept IDroppable items as part of a drop operation.
/// </summary>
public interface IDropContainer
{
/// <summary>
/// Handles the drop of the specified IDroppable item onto the container.
/// </summary>
/// <param name="item"></param>
void Drop(IDroppable item);
}
}
The DragDropManager (and and associated delegate and event args class) are defined as such:
namespace Lionsguard
{
/// <summary>
/// Represents a class used to manage drag and drop items.
/// </summary>
public class DragDropManager
{
private Panel Host { get; set; }
private Point MousePosition { get; set; }
private UIElement DragCursor { get; set; }
private bool HasProcessedEndDrag { get; set; }
private IDroppable Item { get; set; }
/// <summary>
/// Initializes a new instance of the DragDropManager class.
/// </summary>
/// <param name="host">The hosting panel where the drag cursor instance will be added.</param>
public DragDropManager(Panel host)
{
this.Host = host;
this.Host.LostFocus += new RoutedEventHandler(OnHostLostFocus);
}
private void OnHostLostFocus(object sender, RoutedEventArgs e)
{
EndDrag(false);
}
private void OnDragCursorLostMouseCapture(object sender, MouseEventArgs e)
{
EndDrag(false);
}
private void OnDragCursorLostFocus(object sender, RoutedEventArgs e)
{
EndDrag(false);
}
private void OnDragCursorMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
((UIElement)sender).ReleaseMouseCapture();
this.MousePosition = e.GetPosition(null);
this.EndDrag(true);
}
private void OnDragCursorMouseMove(object sender, MouseEventArgs e)
{
Point position = e.GetPosition(null);
double deltaX = position.X - this.MousePosition.X;
double deltaY = position.Y - this.MousePosition.Y;
Point newPosition = new Point(
((double)this.DragCursor.GetValue(Canvas.LeftProperty)) + deltaX,
((double)this.DragCursor.GetValue(Canvas.TopProperty)) + deltaY);
this.DragCursor.SetValue(Canvas.LeftProperty, newPosition.X);
this.DragCursor.SetValue(Canvas.TopProperty, newPosition.Y);
this.MousePosition = position;
}
/// <summary>
/// Begins a drag operation for the specified IDroppable instance starting at the specified mousePosition.
/// </summary>
/// <param name="droppable">The IDroppable instance to being dragging.</param>
/// <param name="mousePosition">The Point at which the drag operation will begin.</param>
public void BeginDrag(IDroppable droppable, Point mousePosition)
{
if (droppable != null && this.Host != null)
{
System.Diagnostics.Debug.WriteLine("Begin Drag...");
this.Item = droppable;
this.DragCursor = droppable.GetDragCursor();
this.DragCursor.LostFocus += new RoutedEventHandler(OnDragCursorLostFocus);
this.DragCursor.LostMouseCapture += new MouseEventHandler(OnDragCursorLostMouseCapture);
// Add the drag cursor to the host control and set the z-index very high.
this.DragCursor.SetValue(Canvas.ZIndexProperty, 5000);
this.Host.Children.Add(this.DragCursor);
this.Host.Cursor = Cursors.Hand;
this.MousePosition = mousePosition;
this.DragCursor.SetValue(Canvas.LeftProperty, this.MousePosition.X);
this.DragCursor.SetValue(Canvas.TopProperty, this.MousePosition.Y);
this.DragCursor.Visibility = Visibility.Visible;
this.HasProcessedEndDrag = false;
this.DragCursor.CaptureMouse();
this.DragCursor.MouseLeftButtonUp += new MouseButtonEventHandler(OnDragCursorMouseLeftButtonUp);
this.DragCursor.MouseMove += new MouseEventHandler(OnDragCursorMouseMove);
}
}
private void EndDrag(bool processDrop)
{
// This prevents the mouse up event or the end drag event firing more than once from causing the
// events to bubble up and the drop be handled more than once.
System.Diagnostics.Debug.WriteLine("End Drag...");
if (this.HasProcessedEndDrag) return;
this.HasProcessedEndDrag = true;
// Reshow the cursor and hide the source image.
this.Host.Cursor = Cursors.Arrow;
// Hide the drag cursor.
if (this.DragCursor != null)
{
this.DragCursor.Visibility = Visibility.Collapsed;
this.DragCursor.LostFocus -= new RoutedEventHandler(OnDragCursorLostFocus);
this.DragCursor.LostMouseCapture -= new MouseEventHandler(OnDragCursorLostMouseCapture);
this.DragCursor.MouseLeftButtonUp -= new MouseButtonEventHandler(OnDragCursorMouseLeftButtonUp);
this.DragCursor.MouseMove -= new MouseEventHandler(OnDragCursorMouseMove);
}
if (processDrop)
{
System.Diagnostics.Debug.WriteLine("Process Drop...");
// Search within host for an IDropContainer instance within the area of the current mouse position.
var container = (from v in VisualTreeHelper.FindElementsInHostCoordinates(this.MousePosition, this.Host)
where (v as IDropContainer) != null
select v as IDropContainer).FirstOrDefault();
if (container != null)
{
System.Diagnostics.Debug.WriteLine("Drop...");
container.Drop(this.Item);
}
}
}
}
/// <summary>
/// Provides a delegate for handling IDroppable.BeginDrag events.
/// </summary>
/// <param name="sender">The IDroppable initiating the drag operation.</param>
/// <param name="e">Event data for the operation.</param>
public delegate void BeginDragEventHandler(object sender, BeginDragEventArgs e);
/// <summary>
/// Represents event data for a BeginDrag event.
/// </summary>
public class BeginDragEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the IDroppable initiating the event.
/// </summary>
public IDroppable Droppable { get; set; }
/// <summary>
/// Gets or sets the Point at which the drag operation should begin.
/// </summary>
public Point MousePosition { get; set; }
}
}
private void AdminScreen_Loaded(object sender, RoutedEventArgs e){_dragDropManager = new DragDropManager(LayoutRoot);}
avatarItem.BeginDrag += new BeginDragEventHandler(OnDroppableBeginDrag);
When the IDroppable raises the BeginDrag event, on a left mouse button click, this method is called which causes the DragDropManager to being the drag operation for the current IDroppable.
private void OnDroppableBeginDrag(object sender, BeginDragEventArgs e)
{
if (_dragDropManager != null)
{
_dragDropManager.BeginDrag(e.Droppable, e.MousePosition);
}
}
When an IDroppable is dropped onto the very same control this method is executed:
#region IDropContainer Members
public void Drop(IDroppable item)
{
// Do stuff when the object is dropped...
}
#endregion
This is pretty much all there is to it.
4/21/2009