Reputation: 5689
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:
TreeView
TreeViewItem
s (dropped item must go to the back of the list)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
? (They 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?
Upvotes: 3
Views: 6969
Reputation: 31
I think there is a simpler way to determine the drop TreeViewItem.
Inside private void Tree_Drop(object sender, DragEventArgs e)
, the argument DragEventArgs e
contains the field OriginalSource
of data type object
At runtime, it is casted to the data type TextBlock
. Inside OrginalSource
, there is a DataContext
; at runtime, this is casted to the object that is defined in xaml for TreeViewItem
. So you can get the target TreeViewItem
by (e.OriginalSource as TextBlock).DataContext as XXX
.
Upvotes: 1
Reputation: 1388
Here's a simpler more concise way, thanks for the direction though Pieter Breed.
private void Tree_DragOver(object sender, DragEventArgs e) {
TreeViewItem treeViewItem = FindAncestor<TreeViewItem>((DependencyObject)e.OriginalSource);
if (treeViewItem != null) {
treeViewItem.Background = Brushes.Blue;
}
}
private void Tree_DragLeave(object sender, DragEventArgs e) {
TreeViewItem treeViewItem = FindAncestor<TreeViewItem>((DependencyObject)e.OriginalSource);
if (treeViewItem != null) {
treeViewItem.Background = Brushes.White;
}
}
private static T FindAncestor<T>(DependencyObject current) where T : DependencyObject { // Search the VisualTree for specified type
while (current != null) {
if (current is T) {
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
}
return null;
}
Upvotes: 3
Reputation: 5689
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)
Upvotes: 6