ClemensGerstung
ClemensGerstung

Reputation: 141

Catel - CommandManager not passing CommandParameter

I have a Catel Application with a "MainView" and some nested views inside. The nested views have a ListView with some items which have a ContextMenu with some MenuItems.

In the ViewModel of the MainView I've created a TaskCommand<object> which will do something with the passed parameter. This passed parameter should be the currently SelectedItem of the ListView. This Command is being registered globally to the ICommandManager.

If I click on the MenuItem with the bound Command from the ICommandManager, the passed parameter will always be null.

Here the relevant code:

NestedView.xaml:

<catel:UserControl
  x:Name="UserControl"
  x:Class="My.NameSpace.Views.View"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:catel="http://catel.codeplex.com">

...
  <ListView ItemsSource="{Binding Items}"
            BorderThickness="0"
            SelectedItem="{Binding SelectedItem}"
            HorizontalContentAlignment="Stretch">
    <ItemsControl.ItemTemplate>
      <DataTemplate>
        <TextBlock VerticalAlignment="Center"
                   Margin="2.5"
                   Text="{Binding Description}">
          <TextBlock.ContextMenu>
            <ContextMenu>
              <MenuItem Header="Do Stuff"
                        Command="{catel:CommandManagerBinding DoStuffCommand}"
                        CommandParameter="{Binding DataContext.SelectedItem, ElementName=UserControl}" />
              ...
            </ContextMenu>
          </TextBlock.ContextMenu>
        </TextBlock>
      </DataTemplate>
    </ItemsControl.ItemTemplate>
  </ListView>
...
</catel:UserControl>

MainViewModel.cs:

public class MainViewModel : ViewModelBase {
    ...

    public ICommand DoStuffCommand { get; }

    public MainViewModel(ICommandManager commandManager, IUIVisualizerService uiVisualizerService, IMessageService messageService)
    {
      ...

      DoStuffCommand = new TaskCommand<object>(OnDoStuffCommandExecute);

      commandManager.CreateCommand(nameof(DoStuffCommand));
      commandManager.RegisterCommand(nameof(DoStuffCommand), DoStuffCommand, this);
    }

    private async Task OnDoStuffCommandExecute(object parameter)
    {
      // command needs to be in the MainViewModel because there will be some modification on the MainView based on parameter (adding tabs, etc)
      Debugger.Break();
    }

    ...
}

If you need more code, I can post this as well but this should be enough.

I Also took a look into Catel's CommandManager implementation and found this:

    /// <summary>
    /// Executes the command.
    /// </summary>
    /// <param name="commandName">Name of the command.</param>
    /// <exception cref="ArgumentException">The <paramref name="commandName"/> is <c>null</c> or whitespace.</exception>
    /// <exception cref="InvalidOperationException">The specified command is not created using the <see cref="CreateCommand"/> method.</exception>
    public void ExecuteCommand(string commandName)
    {
        Argument.IsNotNullOrWhitespace("commandName", commandName);

        lock (_lockObject)
        {
            Log.Debug("Executing command '{0}'", commandName);

            if (!_commands.ContainsKey(commandName))
            {
                throw Log.ErrorAndCreateException<InvalidOperationException>("Command '{0}' is not yet created using the CreateCommand method", commandName);
            }

            _commands[commandName].Execute(null);
        }
    }

I assume that this method will be called if I click the MenuItem and would explain the behavior.

Is there any proper solution/workaround for passing a (bound) parameter to my OnExecute method?

Thanks in advance

Upvotes: 0

Views: 479

Answers (2)

ClemensGerstung
ClemensGerstung

Reputation: 141

Okay, I found the solution, but it's actually a little bit strange.

First of all swap the order of Command={...} and CommandParamter={...} (like Geert said). Then after a lot of failing I got it working: Just pass into the CommandParameter-Binding the Markup and nothing else. So my XAML code for the MenuItem looks like this:

  <MenuItem Header="Do Stuff"
            CommandParameter="{Binding}"
            Command="{catel:CommandManagerBinding DoStuffCommand}"/>

EDIT:

Ok now I got a solution (I think). The way above works most the time but may run into problems as @mm8 points out.

So I googled even more and found this: https://stackoverflow.com/a/3668699 (see Update 2)

The in this answer given solution works now fine for me, because I can bind directly to my ViewModel and get the needed values via binding.

Upvotes: 1

Geert van Horrik
Geert van Horrik

Reputation: 5724

  1. Set the CommandParameter binding before the command binding in xaml

    MenuItem Header="Do Stuff" CommandParameter="{Binding DataContext.SelectedItem, ElementName=UserControl}" Command="{catel:CommandManagerBinding DoStuffCommand}"

  2. Keep in mind that your binding context in the menu item is the item, not the view model. If you want to bind to the view model, you need to provide a name to the root grid and bind like this:

    CommandParameter="{Binding DataContext.SelectedItem, ElementName=rootGrid}"
    

Note that using UserControl is not working because the VM is set inside an internal grid (see the docs for an explanation why). So either use UserControl.ViewModel or SomeInnerControl.DataContext

Upvotes: 1

Related Questions