pass contextmenu parent as CommandParameter - wpf

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

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.

How to get controls from ItemTemplate in ItemsControl in code behind?

I have a MenuItem that loads sub menu items in ItemsControl.
<MenuItem SubmenuOpened="MenuItem_OnSubmenuOpened" Style="{StaticResource MenuItemImageStyle}" >
<ItemsControl ItemsSource="{Binding WorkshopList}" x:Name="ItemsControl" HorizontalContentAlignment="Stretch" Margin="-35 0" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding Name}" x:Name="MenuItem" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.LeaveCommand}" CommandParameter="{Binding Id}" Style="{StaticResource MenuItemImageStyle}" Padding="20">
</MenuItem>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</MenuItem>
I want to focus first item of ItemsControl when open submenu.
I use SubmenuOpened event, and want to find first item and focus it. but How do i get controls of ItemsControl.
var container = ItemsControl.ItemContainerGenerator.ContainerFromItem(ItemsControl.Items.CurrentItem) as FrameworkElement;
but container is null.

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

How to set MouseBinding in ItemsControl

I want to call click command on TextBlock, and i do that like this
<TextBlock Text="New Project">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding Path=NewProject}" MouseAction="LeftClick"/>
</TextBlock.InputBindings>
</TextBlock>
And that is work, but i want to do the same in ItemsControl, so
<ItemsControl Margin="8" ItemsSource="{Binding Path=Projects}" Grid.Row="1" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" Style="{StaticResource ClickableTextBlock}" >
<TextBlock.InputBindings>
<MouseBinding Command="{Binding Path=OpenTasks}" MouseAction="LeftClick"/>
</TextBlock.InputBindings>
</TextBlock>
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And that doesn't work, command is not called. How to do that?
The implicit DataContext inside ItemTemplate is the current data item. But the OpenTasks is not part of each data item. As you said in your comment, it's part of your viewmodel. So in this case you have to set some explicit source with RelativeSource for the Binding. It helps walk up the visual tree and target the source you want. In this case you need the DataContext of the ItemsControl. The code should be like this:
<MouseBinding Command="{Binding DataContext.OpenTasks,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"
MouseAction="LeftClick"/>

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

Resources