11
\$\begingroup\$

I need the command I am executing on a datagrids context menu to know which column was clicked on.

XAML

<DataGrid x:Name="dataGrid" ItemsSource="{Binding Table}" ContextMenuOpening="DataGrid_ContextMenuOpening">
    <DataGrid.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Add Column" Command="{Binding AddColumn}"/>
            <MenuItem Header="Remove Column" Command="{Binding RemoveColumn}"/>
        </ContextMenu>
    </DataGrid.ContextMenu>

</DataGrid>

Code behind:

private void DataGrid_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
    var current = e.OriginalSource as DependencyObject;
    while(current != null && !(current is DataGridCell))
    {
        current = VisualTreeHelper.GetParent(current);
    }
    var cell = current as DataGridCell;
    foreach (var item in dataGrid.ContextMenu.Items.OfType<MenuItem>())
    {
        item.CommandParameter = cell.Column.DisplayIndex;
        (item.Command as DelegateCommand<object>)?.RaiseCanExecuteChanged();
    }
}

I'm having to break MVVM by having the view tell the VM command that its execution has changed because the command binding doesn't seem to refresh on the parameter changing. Is there a better way to do this?

\$\endgroup\$
  • \$\begingroup\$ there is no CommandParameter binding as i'm not aware of a way to bind to bind a contextMenu to the sub element its called on, but the issue is that there is no link between the Commnd parameter and the Cimmand CanExecute call even though the parameter has a baring on it the command can execute or not \$\endgroup\$ – MikeT May 15 '17 at 9:46
3
\$\begingroup\$

I'm having to break MVVM by having the view tell the VM command that its execution has changed because the command binding doesn't seem to refresh on the parameter changing.

The fundamental problem here seems to be that MenuItem tests whether (in effect) Command.CanExecute(CommandParameter) to update its own Enabled property when Command changes, but not when CommandParameter changes.

It seems to me that the least inelegant way of working around this (IMO) bug in WPF is to subclass MenuItem and use DependencyProperty.OverrideMetadata to register a callback on CommandParameterProperty which updates an overridden IsEnabledCore property. To really do it properly you'd want to also override metadata for CommandTargetProperty and handle RoutedCommand specially, as in CommandHelpers.CanExecuteCommandSource.

An alternative approach, which would be hackier and require more XAML but wouldn't get quite so deep into the inner workings of WPF, would be an IMultiValueConverter which takes two parameters (the command and the parameter) and returns a parameterless command. Then you could set up multibindings such that a change in the parameter causes the MenuItem's Command to change, triggering its CanExecute test.

Both of these approaches would require changes in the XAML of all the grids, but no per-grid codebehind. They would also eliminate the assumption, which IMO is one of the nastiest problems of the codebehind in the question, that all commands are instances of DelegateCommand<object>.

(The other nasty problem is that if the while loop is broken because current == null, the body of the second loop will throw a NullReferenceException).

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ taken a while but nice to finally have a well thought out relevant answer, the while loop would only have been a nasty problem if current could be null,in reality there was no way to open the context menu over a non cell element in the grid but for robustness i had added a null check on cell after i asked this question \$\endgroup\$ – MikeT Aug 6 '19 at 9:04
  • \$\begingroup\$ i also agree whole heartedly about allowing the binding on none constant parameters that don't cause a binding refresh is definitely a bug, i'm hoping that now WPF has gone open source the community will start updating the 2005 wpf spec even the slightly improved 2008 spec was never incorporated into studio, hardly any wonder it never managed to displace win forms \$\endgroup\$ – MikeT Aug 6 '19 at 9:16
3
\$\begingroup\$

OK several years later and looking back I now know what I was doing wrong

this is a misuse of the command parameter

the command parameter is not a variable that you pass into the command on execution to relay the state of the application its a constant used to specify behaviour.

if your command depends on a variable parameter you should create your own command that declares the variable as a property which can then trigger the correct events that are required to notify the view of the state change

public class ColumnCommand:ICommand,INotifyPropertyChanged
{
    public int ActiveColumn{get;set;}
    //etc;
}

then you could use the parameter to set if the command performs an add or a remove by passing in the string add or remove as the command parameter as this behaviour will not change during the lifespan of the command binding

this then allows the VM to update itself and work as intended

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

You can simplify the search for the current column to:

private void DataGrid_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
  DataGrid dataGrid = sender as DataGrid;

  DataGridColumn currentColumn = dataGrid.CurrentColumn;
  if (currentColumn != null)
  {
     ...
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ i believe that currentColumn is the one that currently possesses the focus not the one under the mouse so would require the user to left click before right clicking to bring up the context menu, so they are not synonymous but if that behaviour is what you want then you are perfect correct \$\endgroup\$ – MikeT Jan 30 at 10:50
  • \$\begingroup\$ @MikeT. That's correct. But yo should carefully consider, if it's a good idea to manipulate columns that isn't the one focused/active. \$\endgroup\$ – Henrik Hansen Jan 30 at 18:15
  • \$\begingroup\$ i'm not disagreeing just saying this like many questions have multiple viewpoints that can make the correct answer wildly different, eg i change the value of column A then right click on Column E and select delete. which is the safest interpretation of the users intention deleting column A or E? i would say in most circumstances that E the source of the manipulation command is the better option than the source of the previous manipulation command, but it can definitely be argued both ways \$\endgroup\$ – MikeT Jan 31 at 16:00
-2
\$\begingroup\$

Here's an example using the CommandParameter. It's a little verbose, but ContextMenu bindings often are.

<DataGrid x:Name="dataGrid" ItemsSource="{Binding Table}">
    <DataGrid.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Remove Column" Command="{Binding RemoveColumn}"
                      CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.CurrentColumn.DisplayIndex}" />
        </ContextMenu>
    </DataGrid.ContextMenu>
</DataGrid>
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Does that refresh the commands CanExecute when you move over a new column? \$\endgroup\$ – MikeT Aug 31 '17 at 16:01
  • \$\begingroup\$ You have presented an alternative solution, but haven't reviewed the code. Please edit to show what aspects of the question code prompted you to write this version, and in what ways it's an improvement over the original. It may be worth (re-)reading How to Answer. \$\endgroup\$ – Toby Speight Jan 30 at 17:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.