0
votes

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?

2
have u checked answers - AnjumSKhan
I still have no answer. I just found a workaround. The question is, why is MouseDown for DataGrid not working, while it's working for eg the Grid panel. - be_mi
What I mentioned is the standard way to show dynamic menu check msdn - AnjumSKhan
ContextMenu has to be a part of something. - AnjumSKhan
The ContextMenu is part of the Grid inside a CellTemplate. But I intend to open the context menu as well when the DataGridRow is selected the the user presses the cursor right key. - be_mi

2 Answers

0
votes

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

0
votes

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.