When developing UI, most of the time we want the controls nicely sized, ordered and aligned automatically. But sometimes it's just fun to put them free floating and be able to push them around. The Canvas panel allows for absolute positioning of the controls on it; however, there's nothing in WPF that would allow us to interact with the control position on it.

Since the Canvas layout is using absolute positioning and we only want to allow changing of the position of, there is no need to implement new panel. We could implement the drag behavior and just attach it to the Canvas using the Ramora DP pattern. This can be done by creating a new class CanvasDragHelper, that will extend Canvas instances behavior.

public class CanvasDragHelper : DependencyObject
{
    protected Canvas owner;
}

We'll start by defining three dependency properties. We need one DP to attach the helper object to the canvas, one to attach to the elements we want to support being dragged around and one that we will set on the Canvas in our XAML in order to enable the behavior:

/// <summary>
/// Property used to cache the drag helper instance. It also removes the necessity of additional storage
/// to hold the drag helper instances
/// </summary>
protected static readonly DependencyPropertyKey CanvasDragHelperPropertyKey = DependencyProperty.RegisterAttachedReadOnly("CanvasDragHelper",
    typeof(CanvasDragHelper), typeof(CanvasDragHelper), new FrameworkPropertyMetadata(null));

public static readonly DependencyProperty CanvasDragHelperProperty = CanvasDragHelperPropertyKey.DependencyProperty;

public static CanvasDragHelper GetCanvasDragHelper(DependencyObject obj)
{
    return (CanvasDragHelper)obj.GetValue(CanvasDragHelperProperty);
}

protected static void SetCanvasDragHelper(DependencyObject obj, CanvasDragHelper value)
{
    obj.SetValue(CanvasDragHelperPropertyKey, value);
}

/// <summary>
/// This is property that specifies if a framework element is draggable
/// </summary>
public static readonly DependencyProperty IsDraggableProperty = DependencyProperty.RegisterAttached("IsDraggable",
   
typeof(bool), typeof(CanvasDragHelper), new FrameworkPropertyMetadata(false));

public static bool GetIsDraggable(DependencyObject obj)
{
   
return (bool)obj.GetValue(IsDraggableProperty);
}

public static void SetIsDraggable(DependencyObject obj, bool value)
{
    obj.SetValue(IsDraggableProperty, value);
}

/// <summary>
/// This is the main property that activates the drag helper. Set this property to true on the canvas to enable the behavior.
/// </summary>
public static readonly DependencyProperty AllowChildrenDragProperty = DependencyProperty.RegisterAttached("AllowChildrenDrag",
   
typeof(bool), typeof(CanvasDragHelper), new FrameworkPropertyMetadata(false, OnAllowChildrenDragChanged));

public static bool GetAllowChildrenDrag(DependencyObject obj)
{
   
return (bool)obj.GetValue(AllowChildrenDragProperty);
}

public static void SetAllowChildrenDrag(DependencyObject obj, bool value)
{
    obj.SetValue(AllowChildrenDragProperty, value);
}

The first two DP are really straightforward - they only hold data in them. The third DP on the other hand has some logic. In it's Changed event handler we need to create the CanvasDragHelper instance and attach it to the Canvas.

/// <summary>
/// Handler for when AllowChildrenDrag changes.
/// If AllowChildrenDrag is set to true, attach a CanvasDragHelper instance
/// (if there's one already, don't do anything)
/// </summary>
/// <param name="target">CanvasDragHelper target. Must be Canvas.</param>
/// <param name="e">Event args</param>
protected static void OnAllowChildrenDragChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
    Canvas element = target
as Canvas;

    Debug.Assert(element != null, "Can only use CanvasDragHelper with a Canvas");

    // Is there a drag helper instance attached?
   
CanvasDragHelper dragHelper = (CanvasDragHelper)GetCanvasDragHelper(element);

    if ((bool)e.NewValue == false)
    {
       
if (dragHelper != null)
        {
            // Yes, let's detach it from the canvas events
           
dragHelper.Detach();

            // ...and remove the instance from the canvas
           
SetCanvasDragHelper(element, null);
        }
    }

    if (dragHelper != null)
    {
       
// Yes, we already have a helper attached, so do noting
       
return;
    }

    // No, create new drag helper instance and attach it to the canvas events
   
dragHelper = new CanvasDragHelper(element);

    // ...and attach the instance to the canvas
   
SetCanvasDragHelper(element, dragHelper);
}

The handler logic creates a new instance and passes the Canvas element to it's constructor. In the constructor we'll subscribe to the Canvas events we are interested. We also need a method to detach from them, if the AllowChildrenDrag property on the Canvas is set to false. And to make GC's life easier, we will actually set that property to false on the Canvas' OnUnloaded event. :-)

protected CanvasDragHelper(Canvas owner)
{
   
this.owner = owner;

    Attach();
}

protected void Attach()
{
   
this.owner.AddHandler(Thumb.DragStartedEvent, new DragStartedEventHandler(OnDragStarted));
   
this.owner.AddHandler(Thumb.DragDeltaEvent, new DragDeltaEventHandler(OnDragDelta));
   
this.owner.AddHandler(Thumb.DragCompletedEvent, new DragCompletedEventHandler(OnDragCompleted));
   
this.owner.AddHandler(Canvas.MouseLeftButtonDownEvent, new MouseButtonEventHandler(OnMouseLeftButtonDown));
   
this.owner.AddHandler(Canvas.MouseLeftButtonUpEvent, new MouseButtonEventHandler(OnMouseLeftButtonUp));
   
this.owner.AddHandler(Canvas.MouseMoveEvent, new MouseEventHandler(OnMouseMove));
   
this.owner.AddHandler(FrameworkElement.UnloadedEvent, new RoutedEventHandler(OnUnloaded));
}

protected void Detach()
{
    this.owner.RemoveHandler(Thumb.DragStartedEvent, new DragStartedEventHandler(OnDragStarted));
    this.owner.RemoveHandler(Thumb.DragDeltaEvent, new DragDeltaEventHandler(OnDragDelta));
   
this.owner.RemoveHandler(Thumb.DragCompletedEvent, new DragCompletedEventHandler(OnDragCompleted));
   
this.owner.RemoveHandler(Canvas.MouseLeftButtonDownEvent, new MouseButtonEventHandler(OnMouseLeftButtonDown));
   
this.owner.RemoveHandler(Canvas.MouseLeftButtonUpEvent, new MouseButtonEventHandler(OnMouseLeftButtonUp));
   
this.owner.RemoveHandler(Canvas.MouseMoveEvent, new MouseEventHandler(OnMouseMove));
   
this.owner.RemoveHandler(FrameworkElement.UnloadedEvent, new RoutedEventHandler(OnUnloaded));
}

public void OnUnloaded(object sender, RoutedEventArgs e)
{
    SetAllowChildrenDrag(
this.owner, false);
}

Now the only thing left is the actual dragging logic, which as you can guess from the above code will be done on the mouse LeftButton and Move events. That code will come in the next post.