RelayCommand not firing on MenuItem click inside DataGridTextColumn WPF MVVM

42 views Asked by At

I want to add new row on right click- Insert menu on EmpName column but it's not firing InsertCommand. I have enclosed the xaml code here. Please tell me what am I missing here.

<Window x:Class="DataGridRowsManagement.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:data="clr-namespace:DataGridRowsManagement"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <data:ViewModel x:Key="EmpVM"></data:ViewModel>
    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource EmpVM}}">
        <DataGrid AutoGenerateColumns="False" Height="287"
                  HorizontalAlignment="Left" Margin="23,12,0,0"
                  Name="dgEmp" VerticalAlignment="Top" Width="657"
                   ItemsSource="{Binding Path=Employees}" ColumnWidth="*"
                  SelectedIndex="{Binding Path=RecordIndex,Mode=TwoWay}">
            <DataGrid.Columns>
                <DataGridTextColumn  Header="EmpNo" Binding="{Binding EmpNo}" />
                <DataGridTemplateColumn Header="EmpName">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding EmpName}" HorizontalAlignment="Stretch">
                                <TextBlock.ContextMenu>
                                    <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
    <MenuItem Command="{Binding InsertCommand}"                                      CommandParameter="{Binding RecordIndex}" Header="Insert"/>
<MenuItem Command="{Binding DeleteCommand}" CommandParameter="{Binding RecordIndex}"  Header="Delete"/>
                                    </ContextMenu>
                                </TextBlock.ContextMenu>
                            </TextBlock>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTextColumn  Header="Salary" Binding="{Binding Salary}" />
                <DataGridTextColumn  Header="Designation" Binding="{Binding Designation}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>
2

There are 2 answers

4
Andrew KeepCoding On

Those bindings are inside a DataTemplate, so those will try to bind to the item that is going to be templated. Also, since this is inside ContextMenu, you need to use the x:Reference.

Try the next:

<Grid
...
  x:Name="RootGrid"

<TextBlock.ContextMenu>
    <ContextMenu>
        <MenuItem Command="{Binding Source={x:Reference Name=RootGrid}, Path=DataContext.InsertCommand}" />
    </ContextMenu>
</TextBlock.ContextMenu>
0
BionicCode On

The DataGridCell is detached from the visual tree of the parent DataGrid and therefore also has its own namescope. That's why any traversal via relative binding or element names don't work.

When working with context menus or menus in general you should always consider to use routed commands as they make commanding in the UI much easier. You can pick any parent element in order to register a CommandBinding for a routed event. The routed command also allows to register a key gesture.

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public static RoutedCommand InsertCommand { get; } 

  static MainWindow()
  {
    MainWindow.InsertCommand = new RoutedCommand(nameof(MainWindow.InsertCommand), typeof(MainWindow));
    var insertKeyGesture = new KeyGesture(Key.Insert, ModifierKeys.Control);
    MainWindow.InsertCommand.InputGestures.Add(insertKeyGesture);
  }

  private void CanExecuteInsertCommand(object sender, CanExecuteRoutedEventArgs e)
  {
    // The DataGrid (sender) is the command source
    var commandSource = (FrameworkElement)sender;
    var viewModel = (ViewModel)commandSource.DataContext;
    e.CanExecute = viewModel.InsertCommand.CanExecute(e.Parameter);
  }

  private void InsertCommandExecuted(object sender, ExecutedRoutedEventArgs e)
  {
    // The DataGrid (sender) is the command source
    var commandSource = (FrameworkElement)sender;
    var viewModel = (ViewModel)commandSource.DataContext;
    viewModel.InsertCommand.Execute(e.Parameter);
  }
}

MainWindow.xaml

<Window>
  <DataGrid>
    <DataGrid.CommandBindings>
      <CommandBinding Command="{x:Static EditingCommands.ToggleInsert}"
                      Executed="InsertCommandExecuted"
                      CanExecute="CanExecuteInsertCommand" />
    </DataGrid.CommandBindings>

    <!-- Register a key gesture with the routed command -->
    <DataGrid.InputBindings>
      <KeyBinding Command="{x:Static local:MainWindow.InsertCommand}" />
    </DataGrid.InputBindings>

    <DataGrid.Columns>
      <DataGridTemplateColumn Header="EmpName">
        <DataGridTemplateColumn.CellTemplate>
          <DataTemplate>
            <TextBlock Text="{Binding EmpName}">
              <TextBlock.ContextMenu>
    
                <!-- The DataContext is the row item! -->
                <ContextMenu>

                  <!-- Attach the routed command -->
                  <MenuItem Command="{x:Static MainWindow.InsertCommand}" 
                            CommandParameter="{Binding RecordIndex}" 
                            Header="Insert"  />
                </ContextMenu>
              </TextBlock.ContextMenu>
            </TextBlock>
          </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
      </DataGridTemplateColumn>
    </DataGrid.Columns>
  </DataGrid>
</Window>