Sunday 5 January 2014

Adding Drag and drop behaviour to ItemsControls Part 3

        Third part of the drag and drop tutorial in WPF. I'll start with the mouse move event and checking whether anything is being dragged.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
private static void OnMouseMove(object sender, MouseEventArgs e)
{
    ItemsControl source = (ItemsControl)sender;

    System.Windows.Point currentPos = e.GetPosition(null);
    Vector diff = startPoint - currentPos;

    if (e.LeftButton == MouseButtonState.Pressed &&
       ( Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
         Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
    {                
        object data = GetDataFromSource(source, e.GetPosition(source));

        if (data != null)
        {
            DragDrop.DoDragDrop(source as DependencyObject, data, DragDropEffects.Move);
        }                
    }
}

         Above handler uses GetDataFromSource method for retrieving the dragged data from the source control. The method finds the UIElement that has been clicked, checks in the loop, if the item is indeed in the ItemsControl collection and generates adequate dto that is gonna be transferred between collections. In case the SourceControl is reached in the loop it means that the particular item has not been found.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
private static object GetDataFromSource(ItemsControl source, System.Windows.Point point)
{
    UIElement element = source.InputHitTest(point) as UIElement;

    if (element != null)
    {   
        object data = DependencyProperty.UnsetValue;
        while (data == DependencyProperty.UnsetValue)
        {
            element = VisualTreeHelper.GetParent(element) as UIElement;
            if (element == source)                    
                return null;
            
            data = source.ItemContainerGenerator.ItemFromContainer(element);            
        }

        if (data != DependencyProperty.UnsetValue)                
            return data;                
    }
    return null;
}

        Then comes the method for handling drop events, we need to take notice of several special cases, most are commented the rest self explanatory.

 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
private static void Drop(object sender, DragEventArgs e)
{
    ItemsControl parent = (ItemsControl)sender;

    // Checking if there's a group that has control assigned to it
    if (!dragSources[GetGroupName(parent as DependencyObject)].Any(p => p == parent))
        return;
    
    // Get the type of data that's used for the object transfered between containers
    var dataType = parent.ItemsSource.GetType().GetGenericArguments().Single(); 
    // Aquiring data of the particular type
    var data = e.Data.GetData(dataType);                                        

    // This will hit when we'll try to drop garbage into the container
    if (data == null)   
        return;

    // We don't wanna drop the same data to the same control again
    if (((IList)parent.ItemsSource).Contains(data)) 
        return;

    var foundControl = dragSources[GetGroupName(parent as DependencyObject)]
        .Find(p => ((IList)p.ItemsSource).Contains(data));

    if (foundControl == null)
        return;

    ((IList)foundControl.ItemsSource).Remove(data);
    ((IList)parent.ItemsSource).Add(data);
    BindingOperations.GetBindingExpressionBase(parent as DependencyObject, 
        ItemsControl.ItemsSourceProperty).UpdateTarget();
    BindingOperations.GetBindingExpressionBase(foundControl as DependencyObject,
        ItemsControl.ItemsSourceProperty).UpdateTarget();
}

        Last thing left is to add the code for several more handlers we've already used in the previous post. Except for the OnLeftButtonDown, they are all used to prepare the container as a drag and drop container after it becomes visible to the user and remove it from the drag and drop mechanism when it is being hidden from the user. Hence every time the window with such container is being brought forth all adorned ItemsControls are being added to the behaviour and removed when for instance the window containing them hides.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private static void OnLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    startPoint = e.GetPosition(null);
}

private static void DragSource_Loaded(object sender, RoutedEventArgs e)
{
    Add(dragSources, sender);
}

private static void DragSource_Unloaded(object sender, RoutedEventArgs e)
{
    Remove(dragSources, sender);
}

private static void DropTarget_Loaded(object sender, RoutedEventArgs e)
{
    Add(dropTargets, sender);
}

private static void DropTarget_Unloaded(object sender, RoutedEventArgs e)
{
    Remove(dropTargets, sender);
}

       The last handler is the one servicing the drag and drop group name changes, in case the group name is bound to something that changes it during run time. If that happens we need to switch the ItemsControl drag and drop container to a different group.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
private static void GroupNameUpdated(DependencyObject dp, DependencyPropertyChangedEventArgs args)
{
    var itemsControl = dp as ItemsControl;
    string newGroupName = (string)args.NewValue;

    InitializeDragDropCollections();

    if (!dragSources.Any(p => p.Key == newGroupName))
        dragSources.Add((String)args.NewValue, new List<ItemsControl>());
    if (!dropTargets.Any(p => p.Key == newGroupName))
        dropTargets.Add((String)args.NewValue, new List<ItemsControl>());

    var foundCollection = dragSources.Where(p => p.Value.Any(k => k == itemsControl) == true);
    if (foundCollection != null && foundCollection.Count() > 0)
    {
        foundCollection.First().Value.Remove(itemsControl);
        if (!dragSources[((String)args.NewValue)].Any(p => p == itemsControl))
            dragSources[((String)args.NewValue)].Add(itemsControl);
    }            
}

       If you'd like to get the whole code I added the class to my github account:
       http://github.com/simonkatanski/WpfDragAndDropBehavior.git

No comments:

Post a Comment