Thursday 1 August 2013

Adding Drag and drop behaviour to ItemsControls Part 1

        Because drag and drop in WPF can be quite tricky I though I'm gonna share my implementation (based on other implementations I found online) of it. What I had in mind when I wrote it, was ease of use, especially the xaml side of things.

        This implementation should work with all ItemsControl derived controls (like ListBox or DataGrid). It's a static behaviour that can be hooked up to any ItemsControl in XAML. It is also based on groups. You can only drag and drop between controls added to specific groups. The behaviour internally hold 2 collections with drag sources and drop targets with against specific groups. Additionally there's always a default group created in case the group was not specified in XAML.

Code Snippet
  1. public static class DragAndDropBehaviour
  2. {        
  3. private static Dictionary<String, List<ItemsControl>> dragSources;
  4. private static Dictionary<String, List<ItemsControl>> dropTargets;
  5. // Mouse click coordinates
  6. private static System.Windows.Point startPoint;
  7.  
  8. public static readonly DependencyProperty IsDropTargetProperty =
  9.    DependencyProperty.RegisterAttached("IsDropTarget", typeof(bool),
  10.    typeof(DragAndDropBehaviour),
  11.    new UIPropertyMetadata(false, IsDropTargetUpdated));
  12.  
  13. public static readonly DependencyProperty IsDragSourceProperty =
  14.     DependencyProperty.RegisterAttached("IsDragSource", typeof(bool),
  15.     typeof(DragAndDropBehaviour),
  16.     new UIPropertyMetadata(false, IsDragSourceUpdated));
  17.  
  18. public static readonly DependencyProperty GroupNameProperty =
  19.     DependencyProperty.RegisterAttached("GroupName", typeof(String),
  20.     typeof(DragAndDropBehaviour),
  21.     new UIPropertyMetadata("", GroupNameUpdated));

      Above we've got the 2 collections I mentioned. Each dictionary sets a group name against a list of ItemsControls in that group. Group names from both dictionaries must match to make drag and dropping possible.

      There are 3 attached properties (I'm not their setters and getters on purpose to make it brief). We set IsDropTarget to true on a control if we want to allow it to receive the drop action. Same with IsDragSource for drag sources. Group name should be set to any string we want, which describes the group which participates in the drag and dropping behaviour.

Code Snippet
  1. private static void IsDragSourceUpdated(DependencyObject dp,
  2.     DependencyPropertyChangedEventArgs args)
  3. {          
  4.     InitializeDragLists();
  5.  
  6.     ItemsControl DragSource = dp as ItemsControl;
  7.     
  8.     if ((bool)args.NewValue == true)
  9.     {
  10.         if (dragSources.Any(p => p.Key == GetGroupName(dp)))
  11.         {
  12.             dragSources[GetGroupName(dp)].Add(DragSource);
  13.         }
  14.     }
  15.  
  16.     DragSource.PreviewMouseMove -= new MouseEventHandler(OnMouseMove);
  17.     DragSource.PreviewMouseMove += new MouseEventHandler(OnMouseMove);
  18.  
  19.     DragSource.PreviewMouseLeftButtonDown -=
  20.         new MouseButtonEventHandler(OnLeftButtonDown);
  21.     DragSource.PreviewMouseLeftButtonDown +=
  22.         new MouseButtonEventHandler(OnLeftButtonDown);
  23.  
  24.     DragSource.Unloaded += new RoutedEventHandler(DragSource_Unloaded);
  25.     DragSource.Loaded += new RoutedEventHandler(DragSource_Loaded);
  26. }

        First of the two change callbacka called has to initialize those two collections. We also need to add the control which we have adorned with IsDragSource property to the dragSources collection. Additionally 4 events have to be handled in the drag and drop process:
- PreviewMouseLeftButtonDown - to aquire position starting position of the drag process
- OnMouseMove - we start dragging on move, only, if left mouse button has been clicked and is being pressed. And only, if some minimum distance has been reached.
- Unloaded/Loaded - when the visual tree is loaded or unloaded during our work with the UI, we need to make sure that collections are being emtpied or populated again We don't multiple instances of the same control added to those lists.

In the next part I'll describe the rest of methods used.

No comments:

Post a Comment