none
Treeview, ContextMenu and finding the item where the mouse was clicked

    Question

  • I have a TreeView with a ContextMenu.
    When I click on one of the items in the ContextMenu I want to find out what item in the TreeView is at the position where the ContextMenu was opened.

    I have been trying to find it by calling InputHitTest (on TreeView) with the Mouse coordinates - I think this may be where my problem is I am using Mouse.GetPosition(this) to get the point of the mouse. Anyway InputHitTest returns an IInputElement and it always comes back with the Grid that my TreeView is inside of.

    Am I going around this completely the wrong way? What is the right way?

    Thanks.
    Tuesday, June 27, 2006 3:17 PM

Answers

  • you can do something like this (tv1 is the treeview)

    Point p= ((sender as FrameworkElement).Parent as ContextMenu).TranslatePoint( new Point(0,0),tv1);

    DependencyObject obj= tv1.InputHitTest(p) as DependencyObject;

    TreeViewItem lvi = GetDependencyObjectFromVisualTree(uie , typeof(TreeViewItem)) as TreeViewItem;

    private DependencyObject GetDependencyObjectFromVisualTree(DependencyObject startObject, Type type)

    {

    //Iterate the visual tree to get the parent(ItemsControl) of this control

    DependencyObject parent = startObject;

    while (parent != null)

    {

    if (type.IsInstanceOfType(parent))

    break;

    else

    parent = VisualTreeHelper.GetParent(parent);

    }

    return parent;

    }

    Tuesday, June 27, 2006 5:12 PM

All replies

  • you can do something like this (tv1 is the treeview)

    Point p= ((sender as FrameworkElement).Parent as ContextMenu).TranslatePoint( new Point(0,0),tv1);

    DependencyObject obj= tv1.InputHitTest(p) as DependencyObject;

    TreeViewItem lvi = GetDependencyObjectFromVisualTree(uie , typeof(TreeViewItem)) as TreeViewItem;

    private DependencyObject GetDependencyObjectFromVisualTree(DependencyObject startObject, Type type)

    {

    //Iterate the visual tree to get the parent(ItemsControl) of this control

    DependencyObject parent = startObject;

    while (parent != null)

    {

    if (type.IsInstanceOfType(parent))

    break;

    else

    parent = VisualTreeHelper.GetParent(parent);

    }

    return parent;

    }

    Tuesday, June 27, 2006 5:12 PM
  • That seems like a lot of unecessary code if all you want to know is what item was under the mouse on a right click. All you should need to do is take advantage of event routing like so:

    this.myTreeView.MouseRightButtonUp += new MouseButtonEventHandler(this.myHandler);

    private void myHandler(object sender, MouseButtonEventArgs args)
    {
      TreeViewItem item = args.Source as TreeViewItem;

      if(item != null)
      {
         // do whatever you want with item
      }
    }

    HTH,
    Drew

    • Proposed as answer by Howard Abraham Tuesday, November 02, 2010 8:07 PM
    Tuesday, June 27, 2006 7:09 PM
  • handling the MouseRightButtonUp event saving a reference to the item and using that in the click handler of the context menu is lot easier

    Thanks

    Tuesday, June 27, 2006 7:51 PM
  • Drew the code you posted doesn't seem to work for me.
    The Source property of the arguments is set to the TreeView rather than an Item.

    Lee your code seems to do the trick, though it does seem overly complicated for something I would have thought would be simple! Oh well I will stick with what works for the moment. Hopefully a better solution will reveal itself though.

    Thanks.
    Wednesday, June 28, 2006 1:21 AM
  • The key condition for Drew's code to work is that TreeView items should be added explicitly in Xaml or programmatically:

     

    Code Snippet

    TreeViewItem item = new TreeViewItem();

    item.Header = "some";

    this.treeView.Items.Add(item);

     

    In the case when TreeView items are autogenerated via attaching some DataProvider (e.g. XmlDataProvider) to ItemsSource property this wouldn't work.

    --

    Stas.

    Wednesday, May 16, 2007 6:59 AM
  •  

    I had the same problem as Derek, I had an ObservableCollection bound to the TreeView and the args.Source allways returned the TreeView element and never a TreeViewItem element.

     

    So I used lee's aproach, just had to tune it a bit, since the sender itself is already the ContextMenu, no need to call for the parent. If you leave the code as is, the sender's parent will return the Popup Primitive and you'll get a null value when you do the cast to ContextMenu.

     

    Works fine anyways, thanks Lee.

    Thursday, January 31, 2008 7:33 PM
  • Well that is very weird way to get clicked item imo. Isn't there a simpler way of doing this ?

    I have a TreeView bound to xml data.
    I wanted different ContextMenus for different element types, finally I decided on using following approach:

    1. Set ContextMenuOpening for each treeviewItem via styles
    2. In event handler i get ContextMenu from window resources, set tag property on each menuitem to the treeviewitem that sent event
    3. In menuitem click event handler i get proper treeviewitem from tag property.

    I've tried using Commands in menuitems but for some strange reason they always come disabled regardless on what is set in canExecute handler
    Monday, September 15, 2008 9:02 AM
  • How about a less clunky method (same as first sample, just cleaned):
    private TreeViewItem GetTreeViewItemFromContextMenu(FrameworkElement sender, TreeView treeView)     
    {     
        Point menuClickPoint = ((sender as FrameworkElement).Parent as ContextMenu).TranslatePoint(new Point(0, 0), treeRules);     
       
        // get the first potential treeView object that was hit   
        DependencyObject obj = treeView.InputHitTest(menuClickPoint) as DependencyObject;     
       
        // cycle up the tree until you hit a TreeViewItem   
        while (obj != null && !(obj is TreeViewItem))     
        {     
            obj = VisualTreeHelper.GetParent(obj);     
        }     
        
        return obj as TreeViewItem;     
    }    
     
    Tuesday, November 11, 2008 11:48 PM
  • Stas Barabash said:

    The key condition for Drew's code to work is that TreeView items should be added explicitly in Xaml or programmatically:

    ...

    In the case when TreeView items are autogenerated via attaching some DataProvider (e.g. XmlDataProvider) to ItemsSource property this wouldn't work.

    --

    Stas.


    Just to expand on this, there may be another situation in which Drew's code may be made to work. I have a TreeView that is using HierarchicalDataTemplates to define the contents. It has a ContextMenu in which one of the MenuItems is bound to a Command. At first I found that the command's Execute handler was getting the TreeView itself as both the Source and the OriginalSource. But I discovered that if the clicked item is the TreeView's selected item, then the OriginalSource will be the TreeViewItem. My guess is that this would also be the case with a MenuItem's Clicked handler.

    However, there is one thing about my situation which may make a difference here. My ContextMenu is actually part of a HierarchicalDataTemplate, rather than attached directly to the TreeView.

    It may not suit everyone's purposes to do this, but you can make the right-click select the item before the context menu is shown, by adding a MouseRightButtonDown handler as shown in this thread. In my case this provides exactly the behavior I wanted.
    Tuesday, November 25, 2008 10:54 PM
  • I do something like this. You might only need "PlacementTarget" to get the tvi over which the ContextMenu was opened. For my app I need the DataContext of the item.



            <ContextMenu x:Key="StatePointContextMenu">  
                <MenuItem Header="Add Reactor">  
                    <MenuItem.DataContext> 
                        <Binding   
                                RelativeSource="{RelativeSource AncestorType={x:Type ContextMenu}}" 
                                Path="PlacementTarget.DataContext" 
                            /> 
                    </MenuItem.DataContext> 
                    <MenuItem.Command> 
                        <Binding  
                                Source="{StaticResource CommandBindings}" 
                                Converter="{StaticResource ArrayListObjecttoCommand}" 
                                ConverterParameter="AddNewStatePointReactorCommand" 
                                Mode="OneWay" 
                            /> 
                    </MenuItem.Command> 
                    <MenuItem.CommandParameter> 
                        <Binding /> 
                    </MenuItem.CommandParameter> 
                </MenuItem> 
            </ContextMenu> 
    Wednesday, November 26, 2008 9:27 PM
  •  I've tried using Commands in menuitems but for some strange reason they always come disabled regardless on what is set in canExecute handler

    I always find that's because WPF can't find a command sink (CommandBinding) anywhere up the tree from the element raising the command.

    • Proposed as answer by Caleb Vear Thursday, February 19, 2009 12:44 AM
    Wednesday, November 26, 2008 9:43 PM
  • And yet... there is another way.

    private

     

    void cmsTree_MouseClick(object sender, MouseEventArgs e)
    {
         // Show the context menu when the right mouse button is clicked.
         if (e.Button == MouseButtons.Right)
         {
               cmsTree.Show(
    this.PointToScreen(e.Location));
         }
    }

    Sunday, September 06, 2009 7:18 PM
  • I think this is what the developers intended.

    If you have a TreeView object with TreeViewItems and for each TreeViewItem you specify a ContextMenu and for each MenuItem in the ContextMenu you define a Click handler, here is the code  for the Click handler to identify the TreeViewItem context of the ContextMenu.

    private ContextMenuItem_Click(object sender, RoutedEventArgs e)
    {
         MenuItem contextMenuItem = (MenuItem)sender;
         ContextMenu contextMenu = (ContextMenu)contextMenuItem.Parent;
         if
    (contextMenu.PlacementTarget.GetType() == typeof(TreeViewItem)
    )
         { 
              TreeViewItem originatingTreeViewItem = (TreeViewItem)contextMenu.PlacementTarget;
         }
    }

    Thursday, September 10, 2009 5:14 PM
  • That seems like a lot of unecessary code if all you want to know is what item was under the mouse on a right click. All you should need to do is take advantage of event routing like so:

    this.myTreeView.MouseRightButtonUp += new MouseButtonEventHandler(this.myHandler);

    private void myHandler(object sender, MouseButtonEventArgs args)
    {
      TreeViewItem item = args.Source as TreeViewItem;

      if(item != null)
      {
         // do whatever you want with item
      }
    }

    HTH,
    Drew

    This is exactly what I needed.  Thanks!
    Tuesday, November 02, 2010 8:08 PM
  • In simple scenarios Drew's code will work, however when dealing with containers and templates Source or OriginalSource might be an element INSIDE the actual TreeViewItem.

    To overcome these situations, this is what I came up with:

     

    //I understand this is for MouseDoubleClick and directed at a ListView, but the complication can be just the same
    //this is just an example of how to use it the technique
    private void lvChannels_MouseDoubleClick(object sender, MouseButtonEventArgs e) 
    {
     var obj = e.OriginalSource as DependencyObject;
     if (obj == null) return;
    
     var item = obj.FindVisualAncestor<ListViewItem>();
     if (item == null) return;
    
     /* Now we have an actual item */
    }
    
    // a little helper extension for DependencyObjects
    public static T FindVisualAncestor<T>(this DependencyObject obj) where T : DependencyObject 
    {
     if (obj != null)
     {
     if (obj is T) return (T)obj; //we're already at the root element?
    
     do
     {
      if (obj is Visual || obj is Visual3D)
      obj = VisualTreeHelper.GetParent(obj);
    
      else if (obj is FrameworkElement)
      obj = ((FrameworkElement)obj).Parent;
    
      else if (obj is FrameworkContentElement)
      obj = ((FrameworkContentElement)obj).Parent;
    
      if (obj is T) return (T)obj;
     }
     while (obj != null);
     }
     return null;
    }
    
    

     

    • Edited by SilverCA69 Thursday, August 04, 2011 3:10 AM Edited for clarity
    Thursday, August 04, 2011 3:05 AM