This is part 2 of the WPF drag & drop exploration. Part 1 can be found here. Among other things, this CodeProject sample helped me much.
Yesterday I created a WPF behavior following WPF conventions for custom dependency properties. While trying to add an adorner to my item (more on that later), I noticed many samples were using System.Windows.Interactivity.Behavior<T>, which I thought was Blend-related, but apparently is not. Because it is much more concise to write and provides useful overridable methods, I decided to switch to it instead.
In addition, with the previous code, I had a problem when defining multiple dependency properties on my behavior, so I needed to find a way to solve this also.
The first thing is of course to inherit from Behavior<T>. You will find it in the System.Windows.Interactivity namespace, which is not included in .Net. You can grab it through the Blend SDK, in a Nuget package, or it’s included in MVVM frameworks like Caliburn.Micro or MVVM Light.
1 2 3 4 5 |
using System.Windows.Interactivity; public class DragOnCanvasBehavior : Behavior<DependencyObject> { } |
Then, we can remove the Getxxx and Setxxx methods, and replace them with an actual property (that does the same thing). We can also access the behavior instance from the dependency property variable, so we don’t need the instance singleton anymore. We will, however, need to attach the mouse events to the draggable item through ICommands. Since there are no standard ICommand implementation that we can use, we must create one ourselves. I have copied a “standard” implementation from CodeProject, and you will find many similar implementations if you do a quick Google search:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
public class RelayCommand : ICommand { private readonly Predicate<object> canExecute; private readonly Action<object> execute; public RelayCommand(Action<object> execute) : this(execute, DefaultCanExecute) { } public RelayCommand(Action<object> execute, Predicate<object> canExecute) { if (execute == null) { throw new ArgumentNullException("execute"); } if (canExecute == null) { throw new ArgumentNullException("canExecute"); } this.execute = execute; this.canExecute = canExecute; } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public bool CanExecute(object parameter) { return this.canExecute != null && this.canExecute(parameter); } public void Execute(object parameter) { this.execute(parameter); } private static bool DefaultCanExecute(object parameter) { return true; } } |
This command will be the bridge between the view and the other layers of the application.
Now, on to inheriting from Behavior<T>. You will notice that I have changed a few names here and there, because they weren’t reflecting the reality of the application anymore: IDragDropHandler becomes IDraggable ; the DropHandler property becomes DraggableItem. I also have replaced the mouse event names with descriptive names (OnStartDrag instead of “mouse button down”), especially since we will now be able to bind these handlers to any event.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
public class DragOnCanvasBehavior : Behavior<DependencyObject> { // this registers the DraggableItem property on the behavior public static readonly DependencyProperty DraggableItemProperty = DependencyProperty.RegisterAttached( "DraggableItem", typeof(IDraggable), typeof(DragOnCanvasBehavior), new PropertyMetadata(new PropertyChangedCallback((d, e) => { ((DragOnCanvasBehavior)d).draggable = (IDraggable)e.NewValue; }))); // stores the draggable item instance for further access private IDraggable draggable; // replaces the Getxxx and Setxxx methods public IDraggable DraggableItem { get { return (IDraggable)this.GetValue(DraggableItemProperty); } set { this.SetValue(DraggableItemProperty, value); } } // define the commands available to the view public ICommand Dragging { get; private set; } public ICommand StartDrag { get; private set; } public ICommand StopDrag { get; private set; } // defines what the available commands will do public DragOnCanvasBehavior() { this.StartDrag = new RelayCommand((o) => { this.OnStartDrag(); }); this.StopDrag = new RelayCommand((o) => { this.OnStopDrag(); }); this.Dragging = new RelayCommand((o) => { this.OnDragging(); }); } // actually handle the events private void OnDragging() {} private void OnStartDrag() {} private void OnStopDrag() {} } |
You will notice that we don’t attach the commands to any particular item event anymore. This is because it’s handled by the view. It kind of makes sense, because a different device may have a different method of dragging. The rest of the view can be found in part 1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<ItemsControl.ItemTemplate> <DataTemplate> <Border> <!-- contents --> <i:Interaction.Behaviors> <behaviors:DragOnCanvasBehavior DraggableItem="{Binding}"> <i:Interaction.Triggers> <i:EventTrigger EventName="PreviewMouseLeftButtonDown"> <i:InvokeCommandAction CommandName="StartDrag" /> </i:EventTrigger> <i:EventTrigger EventName="PreviewMouseLeftButtonUp"> <i:InvokeCommandAction CommandName="StopDrag" /> </i:EventTrigger> <i:EventTrigger EventName="PreviewMouseMove"> <i:InvokeCommandAction CommandName="Dragging" /> </i:EventTrigger> </i:Interaction.Triggers> </behaviors:DragOnCanvasBehavior> </i:Interaction.Behaviors> </Border> </DataTemplate> </ItemsControl.ItemTemplate> |
The events handlers are also similar to the ones in part 1. The only difference is that they access the item through Behavior<T> properties. For instance :
1 2 3 4 5 6 7 8 |
private void OnStartDrag() { // use Mouse.GetPosition() to get the mouse coordinates this.mouseStartPosition = Mouse.GetPosition(Application.Current.MainWindow); // cast this.AssociatedObject to get the item control ((UIElement)this.AssociatedObject).CaptureMouse(); } |
That’s it! Your behavior event handlers are now linked through the view, which means better testing, and better flexibility.