I've been tasked with a number of controls/implementations to create for an on going work project - among these are an IDE or Design Tool that allows users to create Dashboards for reporting etc. One of the small cogs that I wanted to utilize in the greater machine is a drag and drop feature allowing users to easily select and assign objects (reports/charts/etc.) from an available pool directly to specific locations within a blank DashBoard template object. In looking at the provided Drag and Drop functionality and in keeping with my usual "I want to prove I can do it myself" style, here's a custom Drag and Drop solution that accomplishes the following:
1) Define Draggable objects, how to represent them visually in a "Dragging" state, and what will be Dropped.
2) Define Dropable objects, and how to add a Dropped, Draggable object.
Our DragDropObject represents the dragging object, stores references to the source IDraggable object, and passes the dropped object to the IDroppable object.
public class DragDropObject : Grid
{
/// <summary>
/// Reference to Source IDraggable object
/// </summary>
public object DragSourceRef { get; set; }
/// <summary>
/// DragDropObject.Constuctor(IDraggable.GetDragContent(), IDraggable)
/// </summary>
/// <param name="element"></param>
/// <param name="dragSourceRef"></param>
public DragDropObject(FrameworkElement content, object dragSourceRef, Point origin)
{
DragSourceRef = dragSourceRef;
this.Opacity = 0.5;
this.Children.Add(content);
this.SetValue(Canvas.LeftProperty, origin.X + (this.ActualWidth / 2));
this.SetValue(Canvas.TopProperty, origin.Y + (this.ActualHeight / 2));
this.CaptureMouse();
this.MouseLeftButtonUp += new MouseButtonEventHandler(DragDropObject_MouseLeftButtonUp);
this.MouseMove += new MouseEventHandler(DragDropObject_MouseMove);
this.SetValue(Canvas.ZIndexProperty, App.AppConfig.ZIndexPriorityCount);
((Canvas)App.Current.RootVisual).Children.Add(this);
}
/// <summary>
/// Translates the DragDropObject with MouseMovement
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void DragDropObject_MouseMove(object sender, MouseEventArgs e)
{
Point newPosition = e.GetPosition((Canvas)App.Current.RootVisual);
this.SetValue(Canvas.LeftProperty, newPosition.X + (this.ActualWidth / 2));
this.SetValue(Canvas.TopProperty, newPosition.Y + (this.ActualHeight / 2));
}
/// <summary>
/// OnMouseUp checks for all interesecting UIElements, returns first that implements IDroppable and invokes IDroppable.AddDropObject()
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void DragDropObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
UIElement target = GetDropTargets(e.GetPosition((Canvas)App.Current.RootVisual));
if (target != null && target.GetType().GetInterface("IDroppable", true) != null
&& DragSourceRef != null && DragSourceRef.GetType().GetInterface("IDraggable", true) != null)
{
((IDroppable)target).AddDropObject(((IDraggable)DragSourceRef).GetDropObject());
((Canvas)App.Current.RootVisual).Children.Remove(this);
}
}
/// <summary>
/// Gets all if any UIElements that implement IDroppable that interesect with the passed Point within the RootVisual
/// </summary>
/// <param name="intersectingPoint"></param>
/// <returns></returns>
private UIElement GetDropTargets(Point intersectingPoint)
{
IEnumerable<UIElement> collidingElements = VisualTreeHelper.FindElementsInHostCoordinates(intersectingPoint, App.Current.RootVisual);
return collidingElements.Where(o => o.GetType().GetInterface("IDroppable", true) != null).First();
}
}
IDraggable interface should be applied to any object that can be dragged, in our case the pool of available objects we will be adding to our DashBoard. We enforce the implementation of a GetDropObject() method that returns whatever we want to actually drop into the target IDroppable object, and an implementation of the PerformDragOperation() function which creates a new DragDropObject at the given Point (I’ve added a mousedown event to the IDraggable object that passes MouseEventArgs e.getPosition(RootVisual).
public interface IDraggable
{
/// <summary>
/// Return object to be dropped into IDroppable object
/// </summary>
/// <returns></returns>
FrameworkElement GetDropObject();
/// <summary>
/// Create new DragDropObject
/// </summary>
void PerformDragOperation(Point origin);
}
IDroppable interface simply enforces the implementation of an AddDropObject function that allows us to decide what to do with the dropped object (how to add it). In my case the DashBoard object is really a grid with template DashBoardItems in each Cell. When we drag a pool item over the template DashBoardItem, we invoke the AddDropObject function (below) that gets the Grid.Column and Grid.Row Properties for the Item and assigns them to the Dropped Item before adding the Dropped item to the DashBoard and removing the template DashBoardItem.
public interface IDroppable
{
/// <summary>
/// Adds the IDraggable object and tranforms/modifies as neccessary, to be invoked from IDraggable object.
/// </summary>
/// <param name="o"></param>
void AddDropObject(FrameworkElement o);
}
Next, I'll see how I can modify this setup to work with ListBoxes, ItemSources, and other Custom Controls.
Quick update, Intro content or Digitalboon.com has been postponed for a few weeks as I've been busy moving this past week. Content has taken a different direction, we're looking at a UI very similair to EVE Online's interface. I've decided against hte cartoonish alien appeal.