The FlowDocumentReader has two menu items in its ContextMenu, Copy and Select All. I'd like to add an additional MenuItem to it and have tried this:
private void FlowDocumentReader_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
flowDocumentReader.ContextMenu.Items.Clear();
MenuItem menuItem = new MenuItem();
menuItem.Header = "Test";
flowDocumentReader.ContextMenu.Items.Add(menuItem);
}
additionally I've tried this:
private void FlowDocumentReader_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
MenuItem menuItem = new MenuItem();
menuItem.Header = "Test";
flowDocumentReader.ContextMenu.Items.Add(menuItem);
}
where I don't clear the items in the context menu and attempt to append it. Neither of these work.
I can create my own menu like so:
private void FlowDocumentReader_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
MenuItem menuItem = new MenuItem();
menuItem.Header = "Test";
flowDocumentReader.ContextMenu.Items.Add(menuItem);
e.Handled = true;
ContextMenu menu = new ContextMenu();
MenuItem a = new MenuItem();
a.Header = "A";
menu.Items.Add(a);
MenuItem b = new MenuItem();
b.Header = "B";
menu.Items.Add(b);
flowDocumentReader.ContextMenu.Items.Clear();
flowDocumentReader.ContextMenu = menu;
menu.IsOpen = true;
}
And that'll show up, but what I'd like to have is the Copy and Select All menu items as well as A and B.
Ideas?
You could also do this in the xaml for the FlowDocument:
<FlowDocument.ContextMenu>
<ContextMenu>
<MenuItem Header="{Resx Copy}" Command="Copy"/>
<MenuItem Header="{Resx SelectAll}" Command="SelectAll"/>
<MenuItem Header="{Resx CustomCommand}" Command="{Binding CustomCommand}"/>
</ContextMenu>
</FlowDocument.ContextMenu>
(Headers conveniently localized thanks to Grant Frisken's Resx Extension) :)
If this needs to be applied to many FlowDocuments, you could also define it in a default style somewhere:
<Style TargetType="FlowDocument">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="{Resx Copy}" Command="Copy"/>
<MenuItem Header="{Resx SelectAll}" Command="SelectAll"/>
<MenuItem Header="{Resx CustomCommand}" Command="{Binding CustomCommand}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
The solution I arrived at was to simply recreate those MenuItems on the new Menu and cancel the display of the built-in ContextMenu that is normally displayed. There are a number of built-in ApplicationCommands which can be incorporated into your own custom ContextMenu and the implementation of this is quite straightforward.
Assume that I've got a ContextMenu created from some method, GetContextMenu(), the following event handler rejects the opening of the built-in ContextMenu and substitutes the one returned from the call to GetContextMenu() and adds in the Copy command (Select All is similar).
private void flowDocumentReader_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
e.Handled = true; // keeps the built-in one from opening
ContextMenu myMenu = GetContextMenu();
MenuItem copyMenuItem = new MenuItem();
copyMenuItem.Command = ApplicationCommand.Copy;
copyMenuItem.CommandTarget = myFlowDocument;
myMenu.Items.Add(copyMenuItem);
ShowMenu(myMenu);
}
private void ShowMenu(ContextMenu menu)
{
menu.Placement = PlacementMode.MousePoint;
menu.PlacementRectangle = new Rect(0.0, 0.0, 0.0, 0.0);
menu.IsOpen = true;
}
Related
I have a context menu open up when I right click on a line I have rendered within WPF. I wanted to increase the leeway allowed to the user for right clicking, so manually checked my lines for their coordinates to check against the right click position, and if we get a hit, open up a context menu that should be exactly the same as if they had right clicked exactly on the line.
The functionality works, however, my two context menus are slightly different. Here's the direct hit context menu:
And the XAML for this context menu:
<Line.ContextMenu>
<ContextMenu>
<Separator>
<Separator.Template>
<ControlTemplate TargetType="Separator">
<StackPanel>
<TextBlock Text="{Binding Name}"></TextBlock>
<Separator/>
</StackPanel>
</ControlTemplate>
</Separator.Template>
</Separator>
<MenuItem Header="Normal" Command="{Binding SetNormalCommand}"></MenuItem>
<MenuItem Header="Reverse" Command="{Binding SetReverseCommand}"></MenuItem>
</ContextMenu>
</Line.ContextMenu>
Here's the context menu when you miss the line but are within the leeway area:
And the manual code I made for this context menu:
if (CheckArea(point.NormalLine, clickX, clickY) || CheckArea(point.ReverseLine, clickX, clickY)) {
MenuItem header = new MenuItem{ Header = point.Name};
MenuItem norm = new MenuItem { Header ="Normal"};
MenuItem reverse = new MenuItem { Header ="Reverse"};
Separator sep = new Separator { };
norm.Command = point.SetNormalCommand;
reverse.Command = point.SetReverseCommand;
contextMenu = new ContextMenu();
contextMenu.Items.Add(header);
contextMenu.Items.Add(sep);
contextMenu.Items.Add(norm);
contextMenu.Items.Add(reverse);
contextMenu.IsOpen = true;
_window.ContextMenu = contextMenu;
break;
}
else {
contextMenu.IsOpen = false;
_window.ContextMenu = null;
}
Is there any code I can add to my manual version that will get me the same style? Thanks in advance.
EDIT: I should mention the 'header' for the context menu on a direct hit is non clickable, whereas the one for the area is clickable, which is another difference.
In your xaml Line.Resources tag you would have your context menu like this:
<ContextMenu x:Key="ContextMenu">
<Separator>
<Separator.Template>
<ControlTemplate TargetType="Separator">
<StackPanel>
<TextBlock Text="{Binding Name}"></TextBlock>
<Separator/>
</StackPanel>
</ControlTemplate>
</Separator.Template>
</Separator>
<MenuItem Header="Normal" Command="{Binding SetNormalCommand}"></MenuItem>
<MenuItem Header="Reverse" Command="{Binding SetReverseCommand}"></MenuItem>
</ContextMenu>
Once you have that you can assign it to a Line like this:
<Line Name="ContextLine" ContextMenu="{DynamicResource ContextMenu}"/>
Then in your code behind, you would do this:
var ctx = this.ContextLine.TryFindResource("ContextMenu") as ContextMenu;
_window.ContextMenu = ctx;
This way you are acquiring the same ContextMenu from Xaml and just appling it through code.
In your C# code you are creating a MenuItem for the Name property but in your XAML code you have defined a DataTemplate for your Seperator where you have binded the Name property. That is why you are getting the difference in the ContextMenu. So in your code behind also you need to define DataTemplate for your Seperator instead of a new MenuItem for the Name property. You can do it as following
if (CheckArea(point.NormalLine, clickX, clickY) || CheckArea(point.ReverseLine, clickX, clickY))
{
MenuItem norm = new MenuItem { Header ="Normal"};
MenuItem reverse = new MenuItem { Header ="Reverse"};
Separator sep = new Separator();
ControlTemplate dt = new ControlTemplate();
FrameworkElementFactory stackPanelElement = new FrameworkElementFactory(typeof(StackPanel));
dt.VisualTree = stackPanelElement;
FrameworkElementFactory txtElement = new FrameworkElementFactory(typeof(Textblock));
txtElement.SetValue(TextBlock.TextProperty, point.Name);
stackPanelElement.AppendChild(txtElement);
FrameworkElementFactory seperatorElement = new FrameworkElementFactory(typeof(Seperator));
stackPanelElement.AppendChild(seperatorElement);
sep.Template = dt;
norm.Command = point.SetNormalCommand;
reverse.Command = point.SetReverseCommand;
contextMenu = new ContextMenu();
contextMenu.Items.Add(sep);
contextMenu.Items.Add(norm);
contextMenu.Items.Add(reverse);
contextMenu.IsOpen = true;
_window.ContextMenu = contextMenu;
break;
}
else
{
contextMenu.IsOpen = false;
_window.ContextMenu = null;
}
I have created a ControlTemplate for a LineSymbol:
<esri:SimpleLineSymbol
x:Key="PolylineSymbol"
Width="3"
>
<esri:SimpleLineSymbol.ControlTemplate>
<ControlTemplate>
<Grid
>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
...
</VisualStateGroup>
<VisualStateGroup x:Name="SelectionStates">
...
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Path
x:Name="Element" Fill="{x:Null}"
Stroke="Navy" StrokeThickness="3"
StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round">
<Path.ContextMenu>
<ContextMenu
x:Name="popUpMenu"
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type esri:Graphic}}}">
<MenuItem
x:Name="miSelect"
Header="Select"
IsCheckable="True"
IsChecked="{Binding Selected, FallbackValue=False}"
/>
...
</ContextMenu>
</Path.ContextMenu>
</Path>
</Grid>
</ControlTemplate>
</esri:SimpleLineSymbol.ControlTemplate>
</esri:SimpleLineSymbol>
Everything works well, except the binding in IsChecked on the "Select" menu item: Since neither Symbol, nor Graphic inherits from FrameworkElement no binding expression will take me from here to the Contained Graphic of this SimpleLineSymbol.
I have also tried a Click event (which gives a sender, a command (which supports a parameter) or a MouseRightButtonDown event (on the graphic,) - no method takes me from the right-clicked point on the path of the Symbol to the containing Graphic...
The DataContext of the Menu looks OK in the designer of VS2012, but at run time it does not works, since the Menu is inside a path defined in the ControlTemplate of a Symbol which is not a FrameworkElement!
I have added a name for the ContextMenu, but I am not able to retrieve it from the ViewModel (where I create the graphic and the symbol; if I were able to do that, I would be able to add the desired datacontext in code:
var graphic = new Graphic { Symbol = Resources["PolylineSymbol"] as SimpleLineSymbol;
var menu = graphic.Symbol.ControlTemplate.FindName("popUpMenu", graphic.Symbol); // ???
menu.DataContext = graphic;
)
Any Ideas, please?
If I understand your problem correctly, it seems as though you have a common problem in WPF. The solution is to utilise a Tag property of your Path to 'pass' the DataContext through to the ContextMenu using the ContextMenu.PlacementTarget property. This Gets or sets the UIElement relative to which the ContextMenu is positioned when it opens. Try this:
<Path x:Name="Element" Fill="{x:Null}" Stroke="Navy" StrokeThickness="3"
StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round"
Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type
esri:Graphic}}}"><!--Use Binding.Path that you need for data here-->
<Path.ContextMenu>
<ContextMenu x:Name="popUpMenu" DataContext="{Binding PlacementTarget.Tag,
RelativeSource={RelativeSource Self}}"><!--Use PlacementTarget.Tag-->
<MenuItem x:Name="miSelect" Header="Select" IsCheckable="True"
IsChecked="{Binding Selected, FallbackValue=False}" />
...
</ContextMenu>
</Path.ContextMenu>
</Path>
The solution was to implement the Opened even of the Context menu. In the code behind I assign the instance of the view model on its DataContext.
<Path.ContextMenu>
<ContextMenu
Opened="PopUpMenu_OnOpened"
>
code behind:
private void PopUpMenu_OnOpened(object sender, RoutedEventArgs e)
{
var menu = sender as ContextMenu;
if (menu != null)
{
menu.DataContext = ViewModel;
}
}
Another challenge was to get the clicked graphic and the clicked point.
The solution was to create properties in the View model and assign both on LeftMouseDown and RightMouseDown.
private Graphic GetPolylineGraphic(ESRI.ArcGIS.Client.Geometry.Geometry geometry = null)
{
var drawLayer = Model.GetDrawLayer(MyMap, "Polyline");
var graphic = new Graphic
{
// clone the resourced PolylineSymbol (from Model)
Symbol = new SimpleLineSymbol
{
Color = PolylineSymbol.Color,
Width = PolylineSymbol.Width,
ControlTemplate = PolylineSymbol.ControlTemplate
}
};
if (geometry != null) graphic.Geometry = geometry;
graphic.MouseLeftButtonDown += GraphicOnMouseLeftButtonDown;
graphic.MouseRightButtonDown += GraphicOnMouseRightButtonDown;
drawLayer.Graphics.Add(graphic);
return graphic;
}
private Graphic m_clickedGraphic;
public Graphic ClickedGraphic
{
get { return m_clickedGraphic; }
set
{
if (!Equals(m_clickedGraphic, value))
{
m_clickedGraphic = value;
OnPropertyChanged(value);
}
}
}
private MapPoint m_clickedPoint;
public MapPoint ClickedPoint
{
get { return m_clickedPoint; }
set
{
if (m_clickedPoint != value)
{
m_clickedPoint = value;
OnPropertyChanged(value);
}
}
}
private void GraphicOnMouseRightButtonDown(object sender, MouseButtonEventArgs args)
{
//// This does not work because GraphicElement is internal!!!
//var s = args.Source;
//ClickedGraphic = ((GraphicElement)(e.Source)).Graphic;
//ClickedPoint = ((GraphicElement)(e.Source)).Origin;
ClickedGraphic = sender as Graphic;
ClickedPoint = MyMap.ScreenToMap(args.GetPosition(MyMap));
//// not here - else context menu won't pop!
//args.Handled = true;
}
private void GraphicOnMouseLeftButtonDown(object sender, MouseButtonEventArgs args)
{
var g = sender as Graphic;
if (g != null)
{
ClickedGraphic = g;
ClickedPoint = MyMap.ScreenToMap(args.GetPosition(MyMap));
// select/unselect the graphic on left click
if (g.Selected) g.UnSelect();
else g.Select();
args.Handled = true;
}
}
To make everything work, I had to clone the symbol.
Title just about says it all.I have a lot of buttons, all of them have the same context menu, I want to determine from the click event which button was rgiht clicked to get there.
This code does not work, placementTarget is null:
private void mi_Click(object sender, RoutedEventArgs e)
{
Button contextMenuEzen = null;
MenuItem mnu = sender as MenuItem;
if (mnu != null)
{
ContextMenu ize =(ContextMenu)mnu.Parent;
contextMenuEzen = ize.PlacementTarget as Button;
}
}
Please help me!
For me, this example works:
XAML
<Window.Resources>
<!-- For all MenuItems set the handler -->
<Style TargetType="{x:Type MenuItem}">
<EventSetter Event="Click" Handler="MenuItem_Click" />
</Style>
<!-- One ContextMenu for all buttons (resource) -->
<ContextMenu x:Key="MyContextMenu">
<MenuItem Header="Click this" />
<MenuItem Header="Two" />
<MenuItem Header="Three" />
</ContextMenu>
</Window.Resources>
<Grid>
<Button x:Name="MyButton1" Width="100" Height="30" Content="MyButton1" ContextMenu="{StaticResource MyContextMenu}" />
<Button x:Name="MyButton2" Margin="0,110,0,0" Width="100" Height="30" Content="MyButton2" ContextMenu="{StaticResource MyContextMenu}" />
</Grid>
Code behind
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem mnu = sender as MenuItem;
Button MyButton = null;
if (mnu != null)
{
ContextMenu MyContextMenu = (ContextMenu)mnu.Parent;
MyButton = MyContextMenu.PlacementTarget as Button;
}
MessageBox.Show(MyButton.Content.ToString());
}
RoutedEvents don't work exactly like regular events - the signature of the handler is EventHandler(object sender, RoutedEventArgs e). The RoutedEventArgs has a property called OriginalSource that represents the element in your UI that was actually clicked. In contrast, the sender parameter will always be the object on which the event handler is registered.
use the ContextMenuService to get the placement target as in the following example:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += (sender, args) => {
RoutedEventHandler clickHandler = delegate(object o, RoutedEventArgs eventArgs) {
var mi = (MenuItem) o;
var contextMenu = (ContextMenu) mi.Parent;
var pTarget = ContextMenuService.GetPlacementTarget(contextMenu) as Button;
// just to make sure
if (pTarget == null) {
return;
}
string message = "You clicked on the button " + pTarget.Content;
MessageBox.Show(message);
};
// create a single instance of the ContextMenu
var cm = new ContextMenu();
for (int i = 0; i < 10; i++) {
var mi = new MenuItem {Header = "Item " + i};
mi.Click += clickHandler;
cm.Items.Add(mi);
}
// create a set of buttons and assign them to the RootVisual(StackPanel)
for (int i = 0; i < 5; i++) {
var button = new Button {Content = "Button " + i, ContextMenu = cm};
this.RootVisual.Children.Add(button);
}
};
}
}
I am creating a set of images dynamically and putting them into a Stack Panel like this :-
Image image = new Image();
image.Name = "image_" + iCounter;
image.Height = 100;
image.Width = 100;
image.Source = bitmap;
image.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
image.Stretch = Stretch.Fill;
image.VerticalAlignment = VerticalAlignment.Top;
//image.MouseDown += new MouseButtonEventHandler(image_MouseDown);
image.ToolTip = "Right-Click for Options";
image.ContextMenu = GetContextMenu();
Separator separator = new Separator();
separator.Name = "separator_" + iCounter;
AddImagesStackPanel.Children.Add(image);
AddImagesStackPanel.Children.Add(separator);
iCounter++;
Then in the Context Menu I have this code :-
private System.Windows.Controls.ContextMenu GetContextMenu()
{
System.Windows.Controls.MenuItem mi1;
System.Windows.Controls.MenuItem mi2;
System.Windows.Controls.ContextMenu _contextMenu = new System.Windows.Controls.ContextMenu();
mi1 = new System.Windows.Controls.MenuItem();
mi1.Header = "Show Normal Size";
mi1.Click += new RoutedEventHandler(ContextMenuItem1_Click);
mi2 = new System.Windows.Controls.MenuItem();
mi2.Header = "Remove image";
mi2.Click += new RoutedEventHandler(ContextMenuItem2_Click);
_contextMenu.Items.Add(mi1);
_contextMenu.Items.Add(mi2);
return _contextMenu;
}
Now I wish to get the selected item when the user right clicks on an image and I have this code :-
private void ContextMenuItem2_Click(object sender, RoutedEventArgs e)
{
object obj = e.OriginalSource;
string imageName = ((System.Windows.Controls.Image)obj).Name;
string[] split = imageName.Split('_');
imageUploads.RemoveAt(Convert.ToInt32(split[1]));
DisplayImagesInStackPanel(imageUploads);
}
But obj does not contain the name of the image since its a RoutedEventArgs. Is there any way I can get the selected item in the context menu?
After discussing this in the comments this should work:
// The binding source.
private readonly ObservableCollection<BitmapImage> _imageList = new ObservableCollection<BitmapImage>();
public ObservableCollection<BitmapImage> ImageList
{
get { return _imageList; }
}
How to display this and set up the ContextMenu:
<ItemsControl ItemsSource="{Binding ImageList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding}" Width="100" Height="100"
HorizontalAlignment="Left" Stretch="Fill"
VerticalAlignment="Top" ToolTip="Right-Click for Options">
<Image.ContextMenu>
<ContextMenu>
<MenuItem Header="Show Normal Size" Click="Image_CM_ShowNormalSize_Click"
Tag="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget}"/> <!-- The placement target is the object to which the context menu belongs, i.e. the image -->
<MenuItem Header="Remove Image" Click="Image_CM_RemoveImage_Click"
Tag="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.DataContext}"/> <!-- The DataContext of the Image is the BitmapImage, which should be removed from the list -->
</ContextMenu>
</Image.ContextMenu>
</Image>
<Separator/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
What the handlers might look like:
private void Image_CM_ShowNormalSize_Click(object sender, RoutedEventArgs e)
{
Image img = (sender as FrameworkElement).Tag as Image;
img.Width = (img.Source as BitmapImage).PixelWidth;
img.Height = (img.Source as BitmapImage).PixelHeight;
}
private void Image_CM_RemoveImage_Click(object sender, RoutedEventArgs e)
{
BitmapImage img = (sender as FrameworkElement).Tag as BitmapImage;
// If the image is removed from the bound list the respective visual elements
// will automatically be removed as well.
ImageList.Remove(img);
}
But obj does not contain the name of the image since its a RoutedEventArgs.
True, but the obj at that point is a MenuItem, if you drill one level down, you can get the image.
Is there any way I can get the selected item in the context menu?
Normally one would load the model classes (Image in your case) through the binding of ItemSource of the Menu (or MenuItem if they are to be submenus) and if one takes that route they can pull the originating item off of the DataContext such as in my case the item was an MRU class item.
private void SelectMRU(object sender, RoutedEventArgs e)
{
var mru = (e.OriginalSource as MenuItem).DataContext as MRU;
var name = mru.Name;
...
}
Because you do things by hand you should load the Tag property of the MenuItem with the Image in question
mi1.Tag = {Image instance in question};
then extract on the event.
var image = (e.OriginalSource as MenuItem).Tag as Image;
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>
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);
}
}
}
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.
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).
ListViewItem item = myListView.SelectedItem as ListViewItem;
Seems to work just fine as the item is selected when you right-click it.