10
\$\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
2
\$\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).

\$\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 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 at 9:16
-1
\$\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>
\$\endgroup\$
  • \$\begingroup\$ Does that refresh the commands CanExecute when you move over a new column? \$\endgroup\$ – MikeT Aug 31 '17 at 16:01

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.