views:

514

answers:

1

I have a WPF treeview with one level of child items. I am using the HierarchicalDataTemplate for the top-level items, so the child items are bound to a backing data list.

When I'm doing dragging and dropping I want to find out where in the target list the new items should go to. I have broken the scenarios down into the following cases:

  1. I am on on empty part of the target treeview
  2. I am hovering on or near one of the top-level treeviewitems (dropped item must go to the back of the list)
  3. I am hovering on one of the child-items, in which case the dropped item must go either to the front or the back of the current item, depending on whether I am hovering on the upper or lower half of the item.

My question is, how do I know which treeviewitem I am hovering over? How do I know if it is a parent-type or a child-type treeviewitem? (The have different DataContext data types) Should I do some kind of hittesting? How do I know which top-level item owns the current child-item I am hovering over?

A: 

After trying a bunch of things I think I've come up with a way to do this. The DragOver and Drop events send you a DragEventArgs parameter. You use this to do hit-testing. If you hittest though, you are not likely to hit the item you want directly. Rather, you are going to be hitting something that forms part of the template of the item. To get to the TreeViewItems you are interested in, you can try and walk up the Visual Tree.

In this example the top-level TreeViewItems are bound to GroupItem instances and the child-nodes are bound to DragItems instances. tv is the name of the TreeView element itself. In this code it is safe to assume that I will find it, since the event handlers are defined on this element.

I created the following code that walks up the tree.

    private void FindDropTarget(
        out TreeViewItem pGroupingNode, 
        out TreeViewItem pItemNode,
        DragEventArgs pDragEventArgs)
    {
        pItemNode = null;
        pGroupingNode = null;

        DependencyObject k = VisualTreeHelper.HitTest(tv, pDragEventArgs.GetPosition(tv)).VisualHit;

        while (k != null)
        {
            if (k is TreeViewItem)
            {
                TreeViewItem treeNode = k as TreeViewItem;
                if (treeNode.DataContext is GroupItem)
                {
                    pGroupingNode = treeNode;
                }
                else if (treeNode.DataContext is DragItems)
                {
                    pItemNode = treeNode;
                }
            } 
            else if (k == tv)
            {
                Console.WriteLine("Found treeview instance");
                return;
            }

            k = VisualTreeHelper.GetParent(k);
        }
    }

Consume it like this. Notice the check for IsVisible which is important:

    private void tv_DragOver(object sender, DragEventArgs e)
    {
        TreeViewItem groupingNode, itemNode;
        FindDropTarget(out groupingNode, out itemNode, e);

        GroupItem groupingData = (groupingNode != null ? groupingNode.DataContext as GroupItem : null);
        DragItems dragItem = (itemNode != null && itemNode.IsVisible ? itemNode.DataContext as DragItems : null);

        Console.WriteLine("Hovering ...");
        Console.WriteLine(
            "Grouping Node = {0}, Item Node = {1}",
            groupingData != null ? groupingData.Title : "null",
            dragItem != null ? dragItem.Id : "null");
    }

If you want to give some kind of visual indication of where the item will drop, consider using an adorner like Bea Stollnitz explains here. You may also consider changing some kind of value on the Bound data classes (like having an IsHovering property which can highlight the item via databinding)

Pieter Breed