[ACCEPTED]-WPF: Displaying a Context Menu for a GridView's Items-contextmenu
Yes, add a ListView.ItemContainerStyle with 18 the Context Menu.
<ListView>
<ListView.Resources>
<ContextMenu x:Key="ItemContextMenu">
...
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnListViewItem_PreviewMouseLeftButtonDown" />
<Setter Property="ContextMenu" Value="{StaticResource ItemContextMenu}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
NOTE: You need to reference 17 the ContextMenu as a resource and cannot 16 define it locally.
This will enable the 15 context menu for the entire row. :)
Also 14 see that I handle the PreviewMouseLeftButtonDown
event so I can ensure 13 the item is focused (and is the currently 12 selected item when you query the ListView). I 11 found that I had to this when changing focus 10 between applications, this may not be true 9 in your case.
Updated
In the code behind file you 8 need to walk-up the visual tree to find 7 the list container item as the original 6 source of the event can be an element of 5 the item template (e.g. a stackpanel).
void OnListViewItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.Handled)
return;
ListViewItem item = MyVisualTreeHelper.FindParent<ListViewItem>((DependencyObject)e.OriginalSource);
if (item == null)
return;
if (item.Focusable && !item.IsFocused)
item.Focus();
}
The 4 MyVisualTreeHelper
that is use a wrapper that I've written 3 to quickly walk the visual tree. A subset 2 is posted below.
public static class MyVisualTreeHelper
{
static bool AlwaysTrue<T>(T obj) { return true; }
/// <summary>
/// Finds a parent of a given item on the visual tree. If the element is a ContentElement or FrameworkElement
/// it will use the logical tree to jump the gap.
/// If not matching item can be found, a null reference is returned.
/// </summary>
/// <typeparam name="T">The type of the element to be found</typeparam>
/// <param name="child">A direct or indirect child of the wanted item.</param>
/// <returns>The first parent item that matches the submitted type parameter. If not matching item can be found, a null reference is returned.</returns>
public static T FindParent<T>(DependencyObject child) where T : DependencyObject
{
return FindParent<T>(child, AlwaysTrue<T>);
}
public static T FindParent<T>(DependencyObject child, Predicate<T> predicate) where T : DependencyObject
{
DependencyObject parent = GetParent(child);
if (parent == null)
return null;
// check if the parent matches the type and predicate we're looking for
if ((parent is T) && (predicate((T)parent)))
return parent as T;
else
return FindParent<T>(parent);
}
static DependencyObject GetParent(DependencyObject child)
{
DependencyObject parent = null;
if (child is Visual || child is Visual3D)
parent = VisualTreeHelper.GetParent(child);
// if fails to find a parent via the visual tree, try to logical tree.
return parent ?? LogicalTreeHelper.GetParent(child);
}
}
I hope this additional 1 information helps.
Dennis
Dennis,
Love the example, however I did not 5 find any need for your Visual Tree Helper...
<ListView.Resources>
<ContextMenu x:Key="ItemContextMenu">
<MenuItem x:Name="menuItem_CopyUsername"
Click="menuItem_CopyUsername_Click"
Header="Copy Username">
<MenuItem.Icon>
<Image Source="/mypgm;component/Images/Copy.png" />
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="menuItem_CopyPassword"
Click="menuItem_CopyPassword_Click"
Header="Copy Password">
<MenuItem.Icon>
<Image Source="/mypgm;component/Images/addclip.png" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem x:Name="menuItem_DeleteCreds"
Click="menuItem_DeleteCreds_Click"
Header="Delete">
<MenuItem.Icon>
<Image Source="/mypgm;component/Images/Delete.png" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource ItemContextMenu}" />
</Style>
</ListView.ItemContainerStyle>
Then 4 inside the MenuItem_Click events I added 3 code that looks like this:
private void menuItem_CopyUsername_Click(object sender, RoutedEventArgs e)
{
Clipboard.SetText(mySelectedItem.Username);
}
mySelectedItem 2 is used on the ListView.SelectedItem:
<ListView x:Name="ListViewCreds" SelectedItem="{Binding mySelectedItem, UpdateSourceTrigger=PropertyChanged}" ....
Please 1 tick me if it helps...
You might be interested in the answers for 9 this SO question - I had the same question but wasn't satisfied 8 with using the mousedown event to capture 7 the item that was clicked upon. Several 6 people has responded with simple and easy 5 to comprehend solutions that you might be 4 interested in.
Summary : You can use the 3 data context to pass the item through to 2 the handler, or a command + command parameter 1 setup.
Here is another approach that uses one shared 17 context menu for all list view items. This 16 time without traversing through visual tree 15 and without depending on ListView's selected 14 item. Also it uses commands which I think 13 are much better than handling click events. The 12 context menu opens directly for clicked 11 list element and element's DataContext is 10 accessible for a command handler as command 9 parameter. Here's the XAML:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.CommandBindings>
<CommandBinding Command="{x:Static ApplicationCommands.Open}" CanExecute="Open_CanExecute" Executed="Open_Executed" />
<CommandBinding Command="{x:Static ApplicationCommands.Print}" CanExecute="Print_CanExecute" Executed="Print_Executed" />
</Window.CommandBindings>
<Grid>
<ListView ItemsSource="{Binding People}">
<ListView.Resources>
<ContextMenu x:Key="cmItemContextMenu">
<MenuItem Header="Open" Command="{x:Static ApplicationCommands.Open}" CommandParameter="{Binding}" />
<MenuItem Header="Print" Command="{x:Static ApplicationCommands.Print}" CommandParameter="{Binding}" />
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource cmItemContextMenu}" />
<EventSetter Event="ContextMenuOpening" Handler="ListViewItem_ContextMenuOpening" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" />
<GridViewColumn Header="Mail" Width="180" DisplayMemberBinding="{Binding Mail}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
And C#:
public partial class MainWindow : Window
{
public MainWindow()
{
People = new ObservableCollection<Person>();
People.Add(new Person() { Name = "Alice", Age = "32", Mail = "alice32@example.com" });
People.Add(new Person() { Name = "Bob", Age = "28", Mail = "bob28@example.com" });
People.Add(new Person() { Name = "George", Age = "33", Mail = "george33@example.com" });
InitializeComponent();
DataContext = this;
}
public ObservableCollection<Person> People { get; }
private void ListViewItem_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
var lvi = (ListViewItem)sender;
var cm = lvi.ContextMenu;
cm.DataContext = lvi.DataContext;
}
private void Open_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = e.Parameter is Person;
}
private void Open_Executed(object sender, ExecutedRoutedEventArgs e)
{
var p = (Person)e.Parameter;
MessageBox.Show($"Opening {p.Name}`s data.", "Open");
}
private void Print_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = e.Parameter is Person;
}
private void Print_Executed(object sender, ExecutedRoutedEventArgs e)
{
var p = (Person)e.Parameter;
MessageBox.Show($"Printing {p.Name}`s data.", "Print");
}
}
The trick 8 is to copy ListViewItem's DataContext to 7 ContextMenu's DataContext when the menu 6 is about to be open, which is done in ListViewItem_ContextMenuOpening
event 5 handler. Parameterless {Binding}
in CommandParameter 4 does the rest.
AFIK it's possible to set 3 ContextMenu's DataContext in XAML using 2 some clever tricks, but in my experience, I 1 think this is a more reliable approach.
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.