CommandParameter with MVVM Light - wpf

I'm trying to get a RelayCommand working with a CommandParameter working using MVVM Light. The command is defined in my viewmodel, and I want to pass the selected ListBox item as the parameter. The command is bound, but the parameter is not. Is this possible?
<UserControl x:Class="Nuggets.Metro.Views.EmployeeListView"
...
DataContext="{Binding EmployeeList,Source={StaticResource Locator}}">
<ListBox x:Name="lstEmployee" ItemsSource="{Binding EmployeeItems}" Style="{StaticResource EmployeeList}" Tag="{Binding EmployeeItems}">
<ListBox.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Edit item" Command="{Binding EditEmployeeCommand}" CommandParameter="{Binding PlacementTarget.SelectedItem,RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
<MenuItem Header="Delete item" Command="{Binding DeleteEmployeeCommand}" CommandParameter="{Binding PlacementTarget.SelectedItem,RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>
</ListBox.ContextMenu>

This should work
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Edit item"
Command="{Binding EditEmployeeCommand}"
CommandParameter="{Binding SelectedItem,ElementName=lstEmployee}"/>
<MenuItem Header="Delete item"
Command="{Binding DeleteEmployeeCommand}"
CommandParameter="{Binding SelectedItem,ElementName=lstEmployee}"/>
</ContextMenu>
Use the Name of your ListBox als ElementName in the binding of the CommandParameter and set the path to SelectedItem.
Update:
The above code does not work for ListBox and ContextMenu, cause they belong to diffrent visual trees. The result is
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=lstEmployee'. BindingExpression:Path=SelectedItem; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'CommandParameter' (type 'Object')
The following XAML does the job. Using the PlacementTarget (that is the ListBox) of the ContextMenu.
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Edit item"
Command="{Binding EditEmployeeCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}"/>
<MenuItem Header="Delete item"
Command="{Binding DeleteEmployeeCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}"/>
</ContextMenu>

This works for TreeView or ListView with MVVM.
The PlacementTarget from the ContextMenu is (here) the Listview
<ListView.ContextMenu>
<ContextMenu>
<MenuItem x:Name="OpenExplorer"Header="ShowFolder"
Command="{Binding ShowExplorer}"
CommandParameter ="{Binding Path=PlacementTarget.SelectedItem,
RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</ListView.ContextMenu>

Related

Unable to use FindAncestor to bind to parent properties with a ContextMenu

I have a ListBox with a ContextMenu and I am trying to bind to the Tag.
I am able to use RelativeSource/FindAncestor to bind to the Tag from the ItemTemplate but this same approach doesn't work for the ContextMenu.
I looked through the Live Visual Tree in Visual Studio and I see the ListBox Items but I don't see the ContextMenu. What is the proper way to do this binding if the ListBox is not an Ancestor of the ContextMenu in the Visual Tree?
Note: I intend to create a ContextMenu in the Page.Resources that I can use in more than one ListBox so I do not want to use ElementName to bind to specific controls.
<ListBox Grid.Row="1"
x:Name="SetupStepsList"
VerticalAlignment="Stretch"
KeyDown="ListBox_KeyDown"
Tag="This is the tag"
Style="{StaticResource GenericListBox}"
SelectedValue="{Binding ActiveStep, Mode=TwoWay}"
ItemContainerStyle="{StaticResource TightListBox}"
ItemsSource="{Binding SelectedStation.SetupSteps, Mode=OneWay}">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=Tag}" >
<MenuItem.Icon>
<iconPacks:PackIconMaterial Kind="Plus"
Style="{StaticResource MenuIconStyle}"/>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</ListBox.ContextMenu>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock AllowDrop="False"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=Tag}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
A ContextMenu is not part of the same visual tree as the ListBox, therefore RelativeSource bindings do not work. You can do this instead:
<MenuItem Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}">
This works, because the PlacementTarget of the ContextMenu is the parent ListBox.

ListView TextBlock context menu is not invoking the bound command

My ListView is binded to ObservableDictionary source. In ListView control, value is bound to TextBlock. The context menu for TextBlock is bound to an event but the event is not being executed on click. Below is the code.
<ListView ItemsSource="{Binding Path=Source, Mode=TwoWay}" Grid.Row="0" Height="300">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Value}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding Path= MenuClicked}" CommandParameter="Delete"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
<MenuItem Header="Rename">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding Path= MenuClicked}" CommandParameter="Rename"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Add">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding Path= MenuClicked}" CommandParameter="Add"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
<MenuItem Header="Import">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding Path= MenuClicked}" CommandParameter="Import"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
</ListView.ContextMenu>
</ListView>
Try to provide a RelativeSource for your CommandBinding. Something like
<i:InvokeCommandAction Command="{Binding Path=DataContext.MenuClicked, RelativeSource={RelativeSource.FindAncestor, AncestorType=Window}}" CommandParameter="Delete"/>
Replace the AncestoryType=Window with AncestoryType=UserControl if your ListView is inside a UserControl
ContextMenu doesn't get same NameScope as in the UIElement they are defined.
So binding (like ElementName, Ancestor) usually creates the problem. You have to set Name scope first in the code behind:
NameScope.SetNameScope(contextMenu, NameScope.GetNameScope(this));
contextMenu is the x:Name of your context menu.
Then you can use relative source binding or any other binding for command. like below:
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x: type ListView}},Path=DataContext.MenuClicked}"
PS: If MenuClicked command is defined in the DataContext of ListView.

pass contextmenu parent as CommandParameter

i have a HierarchicalDataTemplate for a TreeViewItem, in the template i have a contextmenu and i want to pass as CommandParameter the ContextMenu parent - i.e the TreeViewItem owner that was right clicked at the moment, is there a way to do that?
here is my template:
<HierarchicalDataTemplate
x:Key="ServerTemplate"
DataType="{x:Type models:Server}"
ItemsSource="{Binding Channels}"
ItemTemplate="{StaticResource ChannelTemplate}">
<StackPanel
Tag="{Binding DataContext,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}"
Orientation="Horizontal">
<StackPanel.ContextMenu>
<ContextMenu
FontSize="14"
FontFamily="Arial">
<MenuItem
Header="{x:Static p:Resources.ServerOperations_CommunicationSettings}"
Command="{Binding PlacementTarget.Tag.ServerCommunicationSettingCommand, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ContextMenu}}}"
CommandParameter="{Binding Path=Parent, RelativeSource={RelativeSource Mode=Self}}">
</MenuItem>
</ContextMenu>
</StackPanel.ContextMenu>
<Image
Source="{Binding ImageURL, Converter={StaticResource StringToImageConverter}}"
Margin="0,0,2,0"
Height="25"
Width="25"/>
<TextBlock
Text="{Binding ServerName}"
Foreground="White"/>
</StackPanel>
</HierarchicalDataTemplate>
thanks for the help
You can get the TreeViewItem by getting PlacementTarget of ContextMenu which will be StackPanel and its TemplatedParent will be ContentPresenter and its TemplatedParent will be TreeViewItem. So this will work:
CommandParameter="{Binding Path=PlacementTarget.TemplatedParent.TemplatedParent,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ContextMenu}}}"
PlacementTarget (StackPanel) --> TemplatedParent (ContentPresenter) --> TemplatedParent (TreeViewItem)
Ideally it's not a good idea to pass UI components to ViewModel. You should pass data i.e. DataContext of TreeViewItem as you can always play with that.
In case you want to pass Server instance i.e. DataContext of TreeviewItem, you can simply do "{Binding}" since MenuItem will inherit it from StackPanel.
CommandParameter="{Binding}"

WPF contextmenu binding

Cant seem to bind a menuitem in my contextmenu to a command in my viewmodel. I know the contextmenu does not live in the visual tree. For testing purposes i have bound to the same command twice in a button. The first binding works but i can get the second binding in the contextmenu to bind. I can see the binding error in output. Someone has any idea?
<HierarchicalDataTemplate DataType="{x:Type inf:OSiteEquipment}" ItemsSource="{Binding Path=SubSystems, Converter={StaticResource subsystemConverter}}" >
<Button HorizontalContentAlignment="Left"
Command="{Binding DataContext.CommandOpenSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}">
<TextBlock Text="{Binding Path=PartData.Name}" TextTrimming="CharacterEllipsis" />
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Add Category" Command="{Binding Path=Parent.PlacementTarget.Tag.CommandOpenSelected, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</HierarchicalDataTemplate>
Just remove the "Parent" in the Databinding path :
<HierarchicalDataTemplate DataType="{x:Type inf:OSiteEquipment}" ItemsSource="{Binding Path=SubSystems, Converter={StaticResource subsystemConverter}}" >
<Button HorizontalContentAlignment="Left"
Command="{Binding DataContext.CommandOpenSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}">
<TextBlock Text="{Binding Path=PartData.Name}" TextTrimming="CharacterEllipsis" />
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Add Category" Command="{Binding Path=PlacementTarget.Command, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</HierarchicalDataTemplate>
You already find the Parent via the relative source -> Omit the Parent in the path
You did not even set the Tag on the PlacementTarget (Button) -> Set it respectively

Binding ContextMenu Items

I have a treeView with some ContextMenu.
One of the ContextMenu items is a "Add Item" menu Item. this item sholud holds as child list of items, where this list is binded to some ObervableCollcetion.
Each model has "Header" and "IsEnabled". each item in the list add as new MenuItem.
My problem is that only after the first time , all items get the proper IsEnabled value.
when I changing some model data, it doean't changes the visisbiltiy of the meneItem.
I'm sure that there is no bug in the ObervableCollcetion because I also use it in other view and its acts properly.
here is the code:
<TreeView Name="tvSceneTree" ItemsSource="{Binding Converter={StaticResource mlControlSceneTreeVMConverter}}"
Height="auto" AllowDrop="True" SelectedItemChanged="tvSceneTree_SelectedItemChanged">
<TreeView.ContextMenu>
<ContextMenu Name="mainContextMenu" Opened="mainContextMenu_Opened">
<MenuItem Header="Add Item" Name="addItemMenu" ItemsSource="{Binding Path=ControlBoxItems}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding ControlName}" IsEnabled ="{Binding IsEnabled}"
Command="{Binding Source={x:Static ev:ApplicationCommands.AddControlToScene}}" CommandParameter="{Binding}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
<MenuItem Header="Copy" Command="{Binding Source={x:Static ev:ApplicationCommands.Copy}}" />
<MenuItem Header="Paste" Command="{Binding Source={x:Static ev:ApplicationCommands.Paste}}" />
<MenuItem Header="Cut" Command="{Binding Source={x:Static ev:ApplicationCommands.Cut}}" />
<MenuItem Header="Duplicate" IsEnabled="{Binding Path=ControlItem, Converter={StaticResource canDoActionOnControlConverter}, ConverterParameter=CanBeDuplicated}" Command="{Binding Source={x:Static ev:ApplicationCommands.DuplicateControl}}" CommandParameter="{Binding}" />
<MenuItem Header="Delete" IsEnabled="{Binding Path=ControlItem, Converter={StaticResource canDoActionOnControlConverter}, ConverterParameter=CanBeRemove}" Command="{Binding Source={x:Static ev:ApplicationCommands.DeleteControl}}" CommandParameter="{Binding}" />
<MenuItem Header="Add animation" IsEnabled="{Binding Path=ControlItem.AnimationStubs,Converter={StaticResource collectionCountToBoolConverter}}" Command="{Binding Source={x:Static ev:ApplicationCommands.AddAnimation}}" CommandParameter="{Binding}" />
<!--<MenuItem Header="Rename" Command="{Binding Source={x:Static ev:ApplicationCommands.RenameControl}}" CommandParameter="{Binding}" />-->
</ContextMenu>
</TreeView.ContextMenu>

Resources