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
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:
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);}
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.
Debug.Assert(element !=
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);}
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));}
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.