Reputation: 141
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
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}"/>
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
Reputation: 5724
Set the CommandParameter
binding before the command binding in xaml
MenuItem Header="Do Stuff" CommandParameter="{Binding DataContext.SelectedItem, ElementName=UserControl}" Command="{catel:CommandManagerBinding DoStuffCommand}"
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