3

I have a ListView with ContextMenu on each ListViewItem that has Click event, how can I detect in the event handler which Item was clicked in this ContextMenu? I need the item ID.

 <Style TargetType="{x:Type ListViewItem}">

. . .

 <Setter Property="Control.Template">
                <Setter.Value>
                    <ControlTemplate TargetType="tv:TreeListViewItem">
                        <Grid>
                            <Grid.ContextMenu>
                                <ContextMenu>
                                    <MenuItem Header="Open in current tab" Click="MenuItemCurrentTab_Click"/>
                                    <MenuItem Header="Open in new tab" Click="MenuItemNewTab_Click"/>
                                </ContextMenu>
                            </Grid.ContextMenu>
Erez
  • 6,405
  • 14
  • 70
  • 124

4 Answers4

6

See this thread..

Following the same way as the answer from the link you would

<Grid.ContextMenu> 
    <ContextMenu> 
        <MenuItem Header="Open in current tab"
                  Click="MenuItemCurrentTab_Click"
                  CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}"/>

...

private void MenuItemCurrentTab_Click(object sender, RoutedEventArgs e)
{
    MenuItem menuItem = sender as MenuItem;
    if (menuItem != null)
    {
        ContextMenu parentContextMenu = menuItem.CommandParameter as ContextMenu;
        if (parentContextMenu != null)
        {
            ListViewItem listViewItem = parentContextMenu.PlacementTarget as ListViewItem;
        }
    } 
}

UPDATE

Add this to get the parent ListViewItem from the Grid

public T GetVisualParent<T>(object childObject) where T : Visual
{
    DependencyObject child = childObject as DependencyObject;
    while ((child != null) && !(child is T))
    {
        child = VisualTreeHelper.GetParent(child);
    }
    return child as T;
}

private void MenuItemCurrentTab_Click(object sender, RoutedEventArgs e)
{
    MenuItem menuItem = sender as MenuItem;
    if (menuItem != null)
    {
        ContextMenu parentContextMenu = menuItem.CommandParameter as ContextMenu;
        if (parentContextMenu != null)
        {
            Grid grid = parentContextMenu.PlacementTarget as Grid;
            ListViewItem listViewItem = GetVisualParent<ListViewItem>(grid);
        }
    } 
}
Community
  • 1
  • 1
Fredrik Hedblad
  • 83,499
  • 23
  • 264
  • 266
  • I triedit and since I have a control template, instead of the listview item parentContextMenu.PlacementTarget retrieves for me the grid that inside the control template :( – Erez Oct 25 '10 at 15:55
3
    private void MenuItemCurrentTab_Click(object sender, RoutedEventArgs e)
    {
        MenuItem menuItem = (MenuItem)e.Source;
        ContextMenu menu = (ContextMenu)menuItem.Parent;
        ListViewItem item = (ListViewItem)menu.PlacementTarget;
        // do something with item
    }

But it's probably better idea to create single ContextMenu, give it proper name, and use it for all list view items.

Athari
  • 33,702
  • 16
  • 105
  • 146
  • I did what you wrote but menu.PlacementTarget retrieves grid and not the listview item and I didn't know what to do with that. – Erez Oct 25 '10 at 15:38
1

A recurring problem, with many attempts to solve but all have their drawbacks. The accepted answer here, for instance, supposes that each ListViewItem has its own ContextMenu. This works but, especially with a larger number of list items, has a considerable cost in XAML complexity and can be slow. And really isn't necessary at all. If we only use a single ContextMenu on the ListView itself, some other solutions suggest to use

 <MenuItem CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />

which seems to solve the problem at first sight (PlacementTarget points to the ListView, its SelectedItem points to the list item, so the menu item handler can use the CommandParameter to get the originating list item), but, unfortunately, fails if the ListView has multiple selection enabled (SelectedItem will point to one of the items selected but not necessarily the one currently clicked) or if we use ListView.PreviewMouseRightButtonDown to disable the selection on right-click (which is, arguably, the only logical thing to do with multiple selections).

There is, however, an approach that has all the benefits:

  • single ContextMenu on the ListView itself;
  • works with all selection schemes, single, multiple, disabled;
  • even with multiple selection, it will pass the currently hovered item to the handler.

Consider this ListView:

<ListView ContextMenuOpening="ListView_ContextMenuOpening">
  <ListView.ContextMenu>
    <ContextMenu>
      <MenuItem Header="Menu1" Click="Menu1_Click" CommandParameter="{Binding Parent, RelativeSource={RelativeSource Self}}" />
    </ContextMenu>
  </ListView.ContextMenu>
</ListView>

The CommandParameter is used to pass the parent of the MenuItem, ie. the ContextMenu itself. But the main trick comes in the menu opening handler:

private void ListView_ContextMenuOpening(object sender, ContextMenuEventArgs e) {
  var menu = (e.Source as FrameworkElement).ContextMenu;
  menu.Tag = (FrameworkElement)e.OriginalSource;
}

Inside this handler, we still know the original source of the event, the root FrameworkElement of the list item DataTemplate. Let's store it in the Tag of the menu for later retrieval.

private void Menu1_Click(object sender, RoutedEventArgs e) {
  if (sender is MenuItem menu)
    if (menu.CommandParameter is ContextMenu context)
      if (context.Tag is FrameworkElement item)
        if (item.DataContext is DataType data) {
          //process data
        }
}

In the menu click handler, we can look up the original ContextMenu we stored in the command parameter, from that we can look up the root FrameworkElement of the list item that we stored just before, and finally get the object stored in the list item (of type DataType).

Gábor
  • 9,466
  • 3
  • 65
  • 79
0
ListViewItem item = myListView.SelectedItem as ListViewItem;

Seems to work just fine as the item is selected when you right-click it.