WPF contextmenu binding - wpf

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

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.

Handling TreeViews context menu with WPF and MVVM

I have a application that shows a TreeView. The TreeView has a context menu. I use Caliburn.Micro as MVVM framework.
The line cal:Message.Attach="[Event Click]=[Action Remove()]" is required so that something happens if the ContextMenu-Item is clicked. This line makes that Caliburn.Micro searches in the ViewModel of the TreeItem for a method with the name Remove.
But I want that the click on the ContextMenu Item will call the method Remove of the ViewModel of the screen. How to do that?
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem cal:Message.Attach="[Event Click]=[Action Remove()]" Name="Remove" Header="Remove item" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
Try this:
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Tag="{Binding RelativeSource={RelativeSource AncestorType=TreeView}}"
cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.Tag.DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"
cal:Message.Attach="[Event Click]=[Action RemoveResource()]" Name="Remove" Header="Remove item" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
Please refer to my answer here for more information:
Caliburn Micro Action inside ItemContainerStyle - No target found for method
Somebody had the similar problem “Bubbling” events from bound viewmodel goes to parent.
And according to author himself Bind a Command to a Button inside a ListView with Caliburn.Micro.
So just call it by convention or like this:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="{Binding Name}" />
</i:EventTrigger>
</i:Interaction.Triggers>

Triggering WPF MVVM Button in ContextMenu on Return/Enter

I have a ContextMenu inside a TreeView, which contains a TextBox and a Button.
<TreeView ItemsSource="{Binding Folders}">
<TreeView.ContextMenu>
<ContextMenu IsOpen="{Binding Path=PlacementTarget.Tag.DataContext.ContextOpen, Mode=TwoWay, RelativeSource={RelativeSource Self}}">
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding Path=PlacementTarget.Tag.DataContext.AddFolderName, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"></TextBox>
<Button Content="Create here" IsDefault="True"
Command="{Binding Path=PlacementTarget.Tag.DataContext.AddFolderCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}">
</Button>
</StackPanel>
</ContextMenu>
</TreeView.ContextMenu>
<TreeView>
In terms of mouse operation, this works as expected. Right-click brings up the menu, and the user can then fill in the text box and left-click on the button to execute the AddFolderCommand.
However, the users would also like the button to trigger on pressing Enter/Return, so they can just stick with the keyboard after entering the text.
At the moment, pressing Enter/Return causes the ContextMenu to close, and the focus to switch to the TreeView. But the underlying command does not execute.
I have tried setting IsDefault="True" on the button, but its behaviour does not change. There can only be one ContextMenu on the screen open at once. We're using MVVM, so I'd prefer to avoid a code-behind solution if possible. How can I trigger the command on the keypress?
<TreeView ItemsSource="{Binding Folders}">
<TreeView.ContextMenu>
<ContextMenu IsOpen="{Binding Path=PlacementTarget.Tag.DataContext.ContextOpen, Mode=TwoWay, RelativeSource={RelativeSource Self}}">
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding Path=PlacementTarget.Tag.DataContext.AddFolderName, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"></TextBox>
<Button Content="Create here" IsDefault="True"
Command="{Binding Path=PlacementTarget.Tag.DataContext.AddFolderCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}">
</Button>
<StackPanel.InputBindings>
<KeyBinding Gesture="Enter" Command ="{Binding Path=PlacementTarget.Tag.DataContext.AddFolderCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"/>
</StackPanel.InputBindings>
</StackPanel>
</ContextMenu>
</TreeView.ContextMenu>
<TreeView>
This should do the trick without having to even leave the xaml. Note the <StackPanel.InputBindings> part

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}"

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