I'm trying to display a context menu on individual cells of a DataGrid. When the user right-clicks the cell, I want a context menu showing 'copy to clipboard' to appear, and upon clicking it, it copies the cell text to the clipboard. I have code working for TextBlock of a StackPanel, but not for cell of DataGrid. I've taken grek40's solution from Right click and copy content menu on c# WPF datagrid and got it working, however, its not working for the datagrid cells in the code provided below. In the comments of grek40's solution, michauzo suggests looking at Create contextmenus for datagrid rows and modifying it by "passing the item as a command parameter". However, since I am learning WPF, and I am unsure what the solution is here.
The XAML is as follows:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Copy"
Executed="CopyCommand_Executed"
CanExecute="CopyCommand_CanExecute"/>
</Window.CommandBindings>
<Window.Resources>
<ContextMenu x:Key="ctMenu" DataContext="{Binding PlacementTarget,RelativeSource={RelativeSource Self}}">
<MenuItem Header="Copy to clipboard"
Command="ApplicationCommands.Copy"
CommandTarget="{Binding}"
CommandParameter="{Binding Text}"/>
</ContextMenu>
</Window.Resources>
<Grid>
<TabControl Name="MainTabControl">
<TabItem Name="StackPanelTab" Header="StackPanel Tab" Margin="24,-2,-28,0">
<StackPanel>
<TextBlock Text="123" ContextMenu="{StaticResource ctMenu}"/>
<TextBlock Text="456" ContextMenu="{StaticResource ctMenu}"/>
</StackPanel>
</TabItem>
<TabItem Name="DataGridTab" Header="DataGrid Tab" Margin="31,-2,-35,0">
<DataGrid Name="datagrid1">
</DataGrid>
</TabItem>
</TabControl>
</Grid>
</Window>
The code behind is as follows:
using System.Windows;
using System.Windows.Input;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<User> users = new List<User>();
users.Add(new User() { Id = 1, Name = "Jane"});
users.Add(new User() { Id = 2, Name = "Bob" });
datagrid1.ItemsSource = users;
}
private void CopyCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
Clipboard.SetText(e.Parameter as string);
}
private void CopyCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (!string.IsNullOrEmpty(e.Parameter as string))
{
e.CanExecute = true;
e.Handled = true;
}
}
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
}
Have you tried doing something with the PreviewMouseRightButtonDown
event?
<Grid>
<TabControl Name="MainTabControl">
<TabItem Name="StackPanelTab" Header="StackPanel Tab" Margin="24,-2,-28,0">
<StackPanel>
<TextBlock Text="123" ContextMenu="{StaticResource ctMenu}"/>
<TextBlock Text="456" ContextMenu="{StaticResource ctMenu}"/>
</StackPanel>
</TabItem>
<TabItem Name="DataGridTab" Header="DataGrid Tab" Margin="31,-2,-35,0">
<DataGrid
Name="datagrid1"
PreviewMouseRightButtonDown="AnyCell_ContextMenuRequest">
</DataGrid>
</TabItem>
</TabControl>
</Grid>
This basic proof of concept worked when I extended your code:
private void AnyCell_ContextMenuRequest(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is TextBlock block &&
e.OriginalSource.ParentOfType<DataGridCell>() is { } cell)
{
cell.ContextMenu = new();
MenuItem copyItem = new MenuItem
{
Header = "Copy to Clipboard",
CommandParameter = block.Text,
// You could try:
// Command = new RoutedUICommand(),
// Command="ApplicationCommands.Copy",
};
cell.ContextMenu.Items.Add(copyItem);
copyItem.Click += (s, args) =>
{
if (s is MenuItem menuItem &&
menuItem.CommandParameter is string text)
{
Clipboard.SetText(text);
// OR something like:
// ApplicationCommands.Copy?.Execute();
MessageBox.Show(
$"{Clipboard.GetText()} copied!",
"Clipboard",
MessageBoxButton.OK,
MessageBoxImage.Information);
}
};
cell.ContextMenu.IsOpen = true;
}
}
I've added this helper extension that goes up the visual tree to find a parent element of a particular type.
/// <summary>
/// This extension goes 'outside' the MainWindow class the
/// </summary>
static partial class Extensions
{
public static T? ParentOfType<T>(this object @this)
{
if (@this is DependencyObject valid)
{
DependencyObject parent = VisualTreeHelper.GetParent(valid);
while (parent != null)
{
if (parent is T found)
return found;
parent = VisualTreeHelper.GetParent(parent);
}
}
return default;
}
}