1

I have a treeview and im trying to implement a drag and drop functionallity.

<TreeView.ItemContainerStyle>
    <Style TargetType="{x:Type TreeViewItem}">
         <EventSetter Event="TreeViewItem.PreviewMouseLeftButtonDown" Handler="PreviewMouseLeftButtonDown" />
    </Style>
</TreeView.ItemContainerStyle>

Then i have an event in the code behind to handle the click, and start the drag:

void PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    if (sender is TreeViewItem)
    {
       //clone the tree item, create an adorner, apply adorner
       //doDragDrop
    }
}

My problem is since PreviewMouseLftButtonDown is a routed event using the tunneling strategy, when the mouse is clicked on a treenode item, I get the root node first. I am not interested in the root node, only the leaf nodes. My structure is like this:

HeaderNodeObject
   |_LeafnodeObject
   |_LeafNodeOject

So in my event I need to basically not do anything and just proceed if the sender treeViewItem is a HeaderNodeObject. But how can i tell what it is? the sender comes in as a treeViewItem, im not sure how to tell what the object it holds is.

user1336827
  • 1,728
  • 2
  • 15
  • 30
  • `PreviewMouseLeftButtonDown` is direct, not tunneled (http://msdn.microsoft.com/en-us/library/system.windows.uielement.previewmouseleftbuttondown(v=vs.110).aspx) – Daniel Ward Jul 30 '14 at 22:17
  • Also, have you tried identifying the root node specifically by giving it an `x:Name` in the XAML to refer to from the code-behind? – Daniel Ward Jul 30 '14 at 22:24
  • @DanielWard try it, I think you will see it is in fact tunneled. Also my tree is databound, so I don't have access to name the treeviewitems. I came up with a solution that seems to work fine below. Thanks! – user1336827 Jul 31 '14 at 13:24
  • According to that MSDN article, it mimics a tunneled event, but it's actually a direct one that is reraised over and over by each element, which would explain why you couldn't prevent it from happening – Daniel Ward Jul 31 '14 at 13:37
  • FYI, the button-specific mouse events are technically `Direct` events, but they are automatically raised by a thunk when the base event comes in. For example, as `PreviewMouseButtonDown` tunnels down, each UI element it passes through will raise the appropriate `Left` or `Right` button event based on which button triggered the original event. The same happens for the bubbling `MouseButtonDown` event. – Mike Strobel May 15 '18 at 18:34

4 Answers4

2

You should be able to use e.Source as TreeViewItem to retrieve the item that was clicked. You can check it against sender to confirm that you only process the event when it has reached the source.

This should result in the event only being processed for the clicked item:

void PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    if (e.Source is TreeViewItem item && sender == item)
         /* do something */;
}

Note, however, that using the event so bluntly means that you'll receive the event if any part of the item receives a mouse down event, including the expansion button. Thus, be careful to *not* set e.Handled = true, lest you prevent the user from toggling expansion, among other things.

Mike Strobel
  • 25,075
  • 57
  • 69
1

The above solution only works for leafs, and I had to do this for a TreeView where I wanted to process the PreviewMouseLeftDown event on any node in the tree. I finally solved it by ignoring the event for all nodes where a child had mouse over. Like this:

var treeViewItem = sender as TreeViewItem;
if (treeViewItem.Items.OfType<TreeViewItem>().All(item => !item.IsMouseOver))
{
    //do my stuff
}

Sort of a hackish solution, but I couldn't find any better, and at least it gets the job done...

Øystein Kolsrud
  • 359
  • 2
  • 16
0

I solved this fairly simply by just checking if the itemsource on the sender is null. If its null that means its a leaf node:

if (sender is TreeViewItem)
{
   Item = sender as TreeViewItem;

    //make sure we have a leaf node, if we dont just move on.
    if (Item.ItemsSource == null)
    {
     //do my stuff
    }
}
user1336827
  • 1,728
  • 2
  • 15
  • 30
0

Will not call this a duplicate answer but will simply mention that Øystein Kolsrud's answer is as correct as this can be without data binding. I don't have enough reputation or else I would simply comment and up-vote, but "User's" answer will only work for leafs on the tree, while Øystein's answer works for all nodes on the tree, including all the leaf nodes.

My case involved going through a fairly shallow (yet heavily populated) tree of recursively populated 'work items.' I was using a TreeView to display the work flow hierarchy, where a selected node would raise an event that captured the Work ID text from the header of the TreeViewItem, so I could then be able to use that WorkID in a meaningful way. When I wanted this to happen for more than just the root or just the first level of children (leaving out the root), Øystein's answer is the way I had to go. Definitely hackish, but definitely effective.

All of the following code I had (besides his conditional check), was there previously. All I had to do was include his check and everything worked as I initially intended.

Just wanted to share a real-world example of this working and verify his work. This condition works like a charm and saved my butt. Thanks again, Øystein Kolsrud!!

     private void TreeItem_Selected(object sender, RoutedEventArgs e)
    {
        TreeViewItem workItem = sender as TreeViewItem;
        if(workItem.Items.OfType<TreeViewItem>().All(item => !item.IsMouseOver))
        {
            string workItemHeader = workItem.Header.ToString();
            string stopAt = "-";
            currentWorkIDToEdit = workItemHeader.Substring(0, workItemHeader.IndexOf(stopAt));
            workItemToEdit.Content = "Work Item To Edit: " + currentWorkIDToEdit + " ";
        }
    }
Scott Mallon
  • 136
  • 1
  • 9