3

How can I determine TreeViewItem clicked in PreviewMouseDown event?

synergetic
  • 7,756
  • 8
  • 65
  • 106

2 Answers2

8

The following seems to work:

private void myTreeView_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
  TreeViewItem item = GetTreeViewItemClicked((FrameworkElement)e.OriginalSource, 
                                                                       myTreeView);
  ...
}

private TreeViewItem GetTreeViewItemClicked(FrameworkElement sender, TreeView treeView)
{
  Point p = ((sender as FrameworkElement)).TranslatePoint(new Point(0, 0), treeView);
  DependencyObject obj = treeView.InputHitTest(p) as DependencyObject;
  while (obj != null && !(obj is TreeViewItem))
    obj = VisualTreeHelper.GetParent(obj);
  return obj as TreeViewItem;
}
synergetic
  • 7,756
  • 8
  • 65
  • 106
  • This solution will only work if the e.OriginalSource is UIElement (Not needed to be FrameworkElement). But for ContentElement, such as Run and HyperLink, this won't work (You will need to get the first FrameworkElement parent or set IsHitTestVisible="False"). – itaiy Mar 27 '19 at 09:16
  • I have successfully used `Visual` instead of `FrameworkElement` to handle `Run` type elements. – dotNET May 01 '19 at 10:48
3

I originally used an extension method on TreeView that takes a UIElement--the sender of the PreviewMouseDown event--like this:

private void MyTreeView_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    var uiElement = sender as UIElement;
    var treeViewItem = myTreeView.TreeViewItemFromChild(uiElement);
}

Here's the extension method (it checks the child itself in case you clicked right on a TreeViewItem directly)...

public static TreeViewItem TreeViewItemFromChild(this TreeView treeView, UIElement child)
{
    UIElement proposedElement = child;

    while ((proposedElement != null) && !(proposedElement is TreeViewItem))
        proposedElement = VisualTreeHelper.GetParent(proposedElement) as UIElement;

    return proposedElement as TreeViewItem;
}

Update:

However, I've since switched it to a more generic version that I can use anywhere.

public static TAncestor FindAncestor<TAncestor>(this UIElement uiElement)
{
    while ((uiElement != null) && !(uiElement is TAncestor))
        retVal = VisualTreeHelper.GetParent(uiElement) as UIElement;

    return uiElement as TAncestor;
}

That either finds the type you're looking for (again, including checking itself) or returns null

You'd use it in the same PreviewMouseDown handler like so...

private void MyTreeView_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    var uiElement = sender as UIElement;
    var treeViewItem = uiElement.FindAncestor<TreeViewItem>();
}

This came in very handy for when my TreeViewItem had a CheckBox in its template and I wanted to select the item when the user clicked the checkbox which normally swallows the event.

Hope this helps!

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286