C# WPF Create Context Menu for DataGrid Cells - Stack Overflow

admin2025-04-17  4

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=";
        xmlns:x=";
        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; }
    }
}

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; }
    }
}
Share Improve this question edited Feb 1 at 14:10 Sir Rufo 19.1k2 gold badges44 silver badges78 bronze badges asked Feb 1 at 11:57 Bradley CantyBradley Canty 131 silver badge3 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

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; 
    }
}
转载请注明原文地址:http://anycun.com/QandA/1744829584a88201.html