I was inspired by a posting on stackoverflow.com - "How do I drag an image around a canvas in WPF" - where the first response in most peoples heads is to work with mouse up/down/move events, track offset positions and move the UI element around in response - pretty reasonable right?
Seeing as WPF's principles favours composition over inheritance and such like, what if instead we used the power of attached properties to attach new behaviour to a UIElement? Here's how to do it, first the code for the dependency property:
public class DraggableExtender : DependencyObject { // This is the dependency property we're exposing - we'll // access this as DraggableExtender.CanDrag="true"/"false" public static readonly DependencyProperty CanDragProperty = DependencyProperty.RegisterAttached("CanDrag", typeof(bool), typeof(DraggableExtender), new UIPropertyMetadata(false, OnChangeCanDragProperty)); // The expected static setter public static void SetCanDrag(UIElement element, bool o) { element.SetValue(CanDragProperty, o); } // the expected static getter public static bool GetCanDrag(UIElement element) { return (bool) element.GetValue(CanDragProperty); } // This is triggered when the CanDrag property is set. We'll // simply check the element is a UI element and that it is // within a canvas. If it is, we'll hook into the mouse events private static void OnChangeCanDragProperty(DependencyObject d, DependencyPropertyChangedEventArgs e) { UIElement element = d as UIElement; if (element == null) return; if (e.NewValue != e.OldValue) { if ((bool)e.NewValue) { element.PreviewMouseDown += element_PreviewMouseDown; element.PreviewMouseUp += element_PreviewMouseUp; element.PreviewMouseMove += element_PreviewMouseMove; } else { element.PreviewMouseDown -= element_PreviewMouseDown; element.PreviewMouseUp -= element_PreviewMouseUp; element.PreviewMouseMove -= element_PreviewMouseMove; } } } // Determine if we're presently dragging private static bool _isDragging = false; // The offset from the top, left of the item being dragged // versus the original mouse down private static Point _offset; // This is triggered when the mouse button is pressed // on the element being hooked static void element_PreviewMouseDown(object sender, MouseButtonEventArgs e) { // Ensure it's a framework element as we'll need to // get access to the visual tree FrameworkElement element = sender as FrameworkElement; if (element == null) return; // start dragging and get the offset of the mouse // relative to the element _isDragging = true; _offset = e.GetPosition(element); } // This is triggered when the mouse is moved over the element private static void element_PreviewMouseMove(object sender, MouseEventArgs e) { // If we're not dragging, don't bother if (!_isDragging) return; FrameworkElement element = sender as FrameworkElement; if (element == null) return; Canvas canvas = element.Parent as Canvas; if( canvas == null ) return; // Get the position of the mouse relative to the canvas Point mousePoint = e.GetPosition(canvas); // Offset the mouse position by the original offset position mousePoint.Offset(-_offset.X, -_offset.Y); // Move the element on the canvas element.SetValue(Canvas.LeftProperty, mousePoint.X); element.SetValue(Canvas.TopProperty, mousePoint.Y); } // this is triggered when the mouse is released private static void element_PreviewMouseUp(object sender, MouseButtonEventArgs e) { _isDragging = false; } }
As you can see, we hook into the events exposed from the target element whenever we detect the property being changed. This allows us to inject any logic we like!
To use the behaviour, we include the namespace in XAML:
<Window x:Class="WPFFunWithDragging.Window1" xmlns:local="clr-namespace:WPFFunWithDragging"
And then just attach the behaviour to the elements we want to be able to drag like so;
<Canvas> <Image Source="Garden.jpg" Width="50" Canvas.Left="10" Canvas.Top="10" local:DraggableExtender.CanDrag="true"/> </Canvas>
Cool huh? Sample code attached....
No comments:
Post a Comment