简体   繁体   中英

WPF DataGrid: Show dynamic context menu on left click

From time to time I'd like to show a context menu when clicking on a Cell in a DataGrid. I create the ContextMenu programmatically and then display it with ContextMenu.IsOpen=true. In the example below, it works when clicking inside the Grid panel, but it doesn't, wenn clicking on a cell(UIElement inside a cell) of the DataGrid.

That is the difference? What do I need to do to make it work on a DataGridCell as well?

Here comes a demo version, first XAML and below the code behind.

    <Window x:Class="WpfApplication7_delete_me.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApplication7_delete_me"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <Grid MouseDown="Grid_MouseDown" Background="Beige">

        <DataGrid x:Name="dataGrid" HorizontalAlignment="Left"  VerticalAlignment="Top" AutoGenerateColumns="False">

          <DataGrid.Columns>
            <DataGridTemplateColumn Header="Name">
              <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                  <TextBlock Text="{Binding Name}" MouseDown="TextBlock_MouseDown"  />
                </DataTemplate>
              </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
          </DataGrid.Columns>

        </DataGrid>

      </Grid>
    </Window>

Here comes the code behind:

    using System.Collections.Generic;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;

    namespace WpfApplication7_delete_me {
      /// <summary>
      /// Interaction logic for MainWindow.xaml
      /// </summary>
      public partial class MainWindow : Window {
        public MainWindow() {
          InitializeComponent();

          Person p1 = new Person(); p1.Name = "abc";
          Person p2 = new Person(); p2.Name = "1q23";
          List<Person> l = new List<Person>() { p1, p2 };
          dataGrid.ItemsSource = l;
        }

        private void Grid_MouseDown(object sender, MouseButtonEventArgs e) {
          ContextMenu cm = new ContextMenu();
          MenuItem mi = new MenuItem();
          mi.Header = "hallo";
          cm.Items.Add(mi);
          cm.IsOpen = true;
        }

        private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e) {
          ContextMenu cm = new ContextMenu();
          MenuItem mi = new MenuItem();
          mi.Header = "hallo";
          cm.Items.Add(mi);
          cm.IsOpen = true;
        }
      }

      class Person {
        public string Name { get; set; }
      }
    }

After some time I found two solutions:

1) It's working when using PreviewMouseDown instead of MouseDown.

2) Using: Dispatcher.BeginInvoke(new Action(() => { c.IsOpen = true; }), null);

But why is setting IsOpen inside MouseDown event not working?

About the first solution.

there are some issues while using the MouseDown event because the event might be marked as handled by other controls. the PreviewMouseDown is a preview event and it is not marked, hence when you use it , it comes all the way down from the root element and controls to your implementation.

for more information you can read here: MSDN UIElement.MouseDown Event

Define an empty ContextMenu for the DataGrid .

<DataGrid.ContextMenu>
    <ContextMenu x:Name="CtxMenu">                    
    </ContextMenu>
</DataGrid.ContextMenu>

And handle ContextMenuOpening event :

private void DataGrid_ContextMenuOpening_1(object sender, ContextMenuEventArgs e)
    {
        ContextMenu ctxmenu = (sender as DataGrid).ContextMenu;
        // suppress ContextMenu if empty
        e.Handled  = ctxmenu.Items.Count == 0;            
    }

private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
    {
        ContextMenu ctxmenu = Dgrd.ContextMenu;

        MenuItem mi = new MenuItem();
        mi.Header = "hallo";
        ctxmenu.Items.Add(mi);
    }

Better would be to handle PreviewMouseDown event at DataGrid level like this <DataGrid ... DataGridCell.PreviewMouseDown="DataGridCell_MouseDown" ... /> .

That way you get ContextMenu as ContextMenu ctxmenu = (sender as DataGrid).ContextMenu; . Also its good to use preview events if you want to do some initial preparation.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM