Friday, April 3, 2009

How To Do Drag & Drop Using the Visual Tree

First, a shout out to Jesse Liberty who has a great post on the basic Drag & Drop pattern:

http://www.silverlight.net/blogs/jesseliberty/archive/2009/01/13/drag-and-drop-with-managed-code.aspx

When creating a complex application, I have found that attaching a MouseLeftButtonDown, MouseMove and MouseLeftButtonUp to every object that is dragable is problematic. As an alternative, I use the function VisualTreeHelper.FindElementsInHostCoordinates and manage the process from a parent UI element.

This code sets up mouse up and mouse down events for the parent UI object:


public DrawingSurface() : base()
{
MouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown);
MouseLeftButtonUp += new MouseButtonEventHandler(OnMouseLeftButtonUp);
}


The following code lets me hit test against the children objects in the entire VisualTree, looking for an element of type "T", which are UIElements. During the search I am also looking for a panel of type "C", just in case the user is not over an object (for example, selecting nothing). A list of select objects is returned :


public List<T> FindElement<T,C>(Point oPoint, ref Panel oPanel) where T : UIElement
{
List<T> oList = new List<T>();
foreach (UIElement oElement in VisualTreeHelper.FindElementsInHostCoordinate(oPoint, this))
{
T oShape = oElement as T;
if (oShape != null)
oList.Add(oShape);

if (oElement is C && oPanel == null)
oPanel = oElement as Panel;
}
return oList;
}


Here is just one example of some of the hit test logic you can implement. Based on the type of SketchObject that is found, I can infer the user's drawing need -- is the user planning to drag the shape, stretch the shape, connect the shape, or create the shape -- and auto-select the right drawing tool. It is a lot easier to implement this logic in the parent UI Element:


void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Panel oPanel = null;
Point oPoint = e.GetPosition(null);

CurrentTool = DrawingTool.Ignore;

ActiveHandle = FindElement(oPoint, ref oPanel);
if (ActiveHandle.Count != 0)
{
if (ActiveHandle[0] is RotateHandle)
CurrentTool = DrawingTool.Rotate;
}

ActivePort = FindElement(oPoint,ref oPanel);
if (ActivePort.Count != 0)
{
if (CurrentTool == DrawingTool.Ignore)
CurrentTool = DrawingTool.Connect;

OpenDragConnect(ActivePort[0], oPoint);
return;
}

ActiveGroup = FindElement(oPoint, ref oPanel);
if(ActiveGroup.Count != 0)
{
if ( CurrentTool == DrawingTool.Ignore )
CurrentTool = DrawingTool.Move;

OpenDragDropShape(ActiveGroup[0],oPoint);
return;
}

ActiveMaster = FindElement(oPoint, ref oPanel);
if (ActiveMaster.Count != 0)
{
CurrentTool = DrawingTool.Create;
OpenDragDropShape(ActiveMaster[0], oPoint);
return;
}

}


By using this approach you can consolidate all of drag & drop code in one place, making it easier to manage and maintain.

No comments: