Повторяющаяся проблема, со многими попытками решить, но у всех есть свои недостатки. Например, принятый ответ предполагает, что каждый ListViewItem
имеет свой ContextMenu
. Это работает, но, особенно с большим количеством элементов списка, имеет значительную стоимость в сложности XAML и может быть медленным. И действительно не нужно вообще. Если мы используем только один ContextMenu
на самом ListView
, некоторые другие решения предлагают использовать
<MenuItem CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
, который, похоже, решает проблему с первого взгляда (PlacementTarget
указывает на ListView
, его SelectedItem
указывает на элемент списка, поэтому обработчик элемента меню может использовать CommandParameter
для получения исходного элемента списка ), но, к сожалению, происходит сбой, если в ListView
разрешено множественное выделение (SelectedItem
будет указывать на один из выбранных элементов, но не обязательно на тот, который был выбран в данный момент), или если мы используем ListView.PreviewMouseRightButtonDown
для отключения выделения справа click (что, пожалуй, единственная логическая вещь, которую можно сделать с несколькими вариантами выбора).
Однако существует подход, который имеет все преимущества:
- один
ContextMenu
на самом ListView
;
- работает со всеми схемами выбора, одиночной, множественной, отключенной;
- даже с множественным выбором, он передаст в данный момент обработанный элемент обработчику.
Учтите это ListView
:
<ListView ContextMenuOpening="ListView_ContextMenuOpening">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Menu1" Click="Menu1_Click" CommandParameter="{Binding Parent, RelativeSource={RelativeSource Self}}" />
</ContextMenu>
</ListView.ContextMenu>
</ListView>
CommandParameter
используется для передачи родителя MenuItem
, т.е. сам по себе ContextMenu
. Но главный трюк в обработчике открытия меню:
private void ListView_ContextMenuOpening(object sender, ContextMenuEventArgs e) {
var menu = (e.Source as FrameworkElement).ContextMenu;
menu.Tag = (FrameworkElement)e.OriginalSource;
}
Внутри этого обработчика нам все еще известен исходный источник события, корень FrameworkElement
элемента списка DataTemplate
. Давайте сохраним его в Tag
меню для последующего поиска.
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
}
}
В обработчике щелчка меню мы можем найти исходный ContextMenu
, который мы сохранили в параметре команды, из которого мы можем найти корень FrameworkElement
элемента списка, который мы сохранили ранее, и, наконец, получить объект, хранящийся в элементе списка (типа DataType
).