Thursday, April 23, 2009

Dragging In Silverlight

I started writing this blog to help improve my communication skills. I thought it might be interesting to document a startup from the beginning. Much to my chagrin, though totally understandably, this is not why most people end up at my blog. According to Google Analytics, most people only want me for my code. Well, in the words of Seth Godin or the Kinks or Red Skelton, "Give them what they want."

The simplest piece of code I could dig out on short notice was my drag provider. Sure, this one has been done to death, but with the refactoring into a provider and another little convenience or two I thought it might still be nice to share. Before I get to the code I'm going to describe those useful bits as well as some assumptions the code makes. Though you can always scroll down and get what you really came here for.

First, the provider assumes that the drag element is a child of a canvas. This might be obvious since that is the easiest way, but I thought I'd lay it out there. The top most root layout of our application is a 1x1 Grid with child Grids and DockPanels for the real layout. A simple rule that we follow is that the RootLayout of any of our draggable controls must be a canvas. This works out well for us for too many reasons to list here, but in retrospect, you could probably dynamically add the canvas when creating the control. Either way, the end result is a 1x1 Grid that takes up the entire browser with its top most visible layer being the canvas that in turn contains the draggable element.

Second, the PositionDragElement routine in the sample goes to a bit of extra trouble to make sure that the element being dragged around the screen always stays in full view within the Silverlight plugin. If the mouse leaves the plugin, the drag element will follow the mouse around the edges of the plugin until the mouse returns or the button is released. This may not be desired in some cases, but for us, it prevents users from dragging dialogs out of the Silverlight application and not being able to close them.

Third; also about the PositionDragElement routine; it was pulled out of a common class used both in our draggable provider and our drag and drop framework. If something looks out of place or doesn't work quite right, forgive me. I did some on the fly munging to simplify this example a bit. Also, you obviously don't need to use this provider as is. It makes a decent example of how to perform dragging for many situations.

And so, here's the code.

public class DraggableProvider
{
#region Constructors
 public DraggableProvider(FrameworkElement dragElement)
{
if (null == dragElement)
throw new ArgumentNullException("dragElement");
_dragElement = dragElement;

_dragElement.MouseLeftButtonDown += new MouseButtonEventHandler(DragElement_MouseLeftButtonDown);
_dragElement.MouseLeftButtonUp += new MouseButtonEventHandler(DragElement_MouseLeftButtonUp);
_dragElement.MouseMove += new MouseEventHandler(DragElement_MouseMove);
}

#endregion

#region Event Handlers

void DragElement_MouseMove(object sender, MouseEventArgs e)
{
if (_isMouseDown)
{
var currentMousePosition = e.GetPosition(null);
DragHelper.PositionDragElement(currentMousePosition);
_lastMousePosition = currentMousePosition;
}
}

void DragElement_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_isMouseDown = false;
_dragElement.ReleaseMouseCapture();
}

void DragElement_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_isMouseDown = true;
_lastMousePosition = e.GetPosition(null);
_dragElement.CaptureMouse();
}

#endregion

#region Private Fields

private FrameworkElement _dragElement;
private bool _isMouseDown;
private Point _lastMousePosition;

#endregion

#region Private Methods
 private void PositionDragElement(Point currentMousePosition)
{
var xPosition = (double)_dragElement.GetValue(Canvas.LeftProperty);
var yPosition = (double)_dragElement.GetValue(Canvas.TopProperty);
var elementOriginPosition = new Point(xPosition, yPosition);
  var xDelta = currentMousePosition.X - _lastMousePosition.X;
var yDelta = currentMousePosition.Y - _lastMousePosition.Y;
var dragElementWidth = _dragElement.ActualWidth;
var dragElementHeight = _dragElement.ActualHeight;
  // Verify that the drag element contains the mouse.
// This is important when first picking up the element.

var newX = elementOriginPosition.X + xDelta;
if ( currentMousePosition.X < newX )
newX = currentMousePosition.X - .05 * dragElementWidth;
else if (currentMousePosition.X > newX + dragElementWidth)
newX = currentMousePosition.X -
.95 * dragElementWidth;
  var newY = elementOriginPosition.Y + yDelta;
if ( currentMousePosition.Y < newY )
newY
= currentMousePosition.Y - .05 * dragElementHeight;
else if (currentMousePosition.Y > newY + dragElementHeight)
newY = currentMousePosition.Y -
.95 * dragElementHeight;
    // Validate that draggable item is still within the browser.
// This takes precedence over the mouse staying inside the element.
var rootPanel = Application.Current.RootVisual as Panel;
newX = Math.Min(newX, rootPanel.ActualWidth - dragElementWidth);
newX = Math.Max(newX, 0);
newY = Math.Min(newY, rootPanel.ActualHeight - dragElementHeight);
newY = Math.Max(newY, 0);

_dragElement.SetValue(Canvas.LeftProperty, newX);
_dragElement.SetValue(Canvas.TopProperty, newY);
}
 #endregion
}
And now, this is how we make our dialogs draggable. We also, add a background that stretches vertical and horizontal to make the dialog modal. Have fun.
 Application.Current.RootVisual as Panel.Children.Add(Background);
Application.Current.RootVisual as Panel.Children.Add(Dialog);
_draggableProvider = new DraggableProvider(Dialog);

No comments:

Post a Comment