How can I track which element of ItemsControl caused ContexMenu to open? - wpf

In my book editor app I need to display themes and subthemes in an hierarchical order, so I made such an markup:
<StackPanel>
<ScrollViewer>
<Grid ColumnDefinitions="auto, *" RowDefinitions="auto">
<ItemsControl Grid.Column="0" Grid.Row="0" Items="{Binding Book.Themes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="{Binding Name}" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.UpdateSubthemesVisibilityCommand}" CommandParameter="{Binding Name}"/>
<ItemsControl Items="{Binding Subthemes}" IsVisible="{Binding AreSubthemesVisible}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename" />
<MenuItem Header="Delete"/>
</ContextMenu>
</ItemsControl.ContextMenu>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename" Command="{Binding RenameThemeCommand}" CommandParameter="...How to bind it to button which cause menu to open"/>
<MenuItem Header="Delete"/>
</ContextMenu>
</ItemsControl.ContextMenu>
</ItemsControl>
</Grid>
</ScrollViewer>
</StackPanel>
Maybe markup isn't quite clear, but the point is that I have external ItemsControl and internal one. External one's items have structure: button (theme name) + internal ItemsControl which presents subthemes' names as buttons too. Here is Screenshot:
Each ItemsControl has its own ContextMenu, but I can't bind them properly. I want "Rename" MenuItems to be bind to Button.Content of Button which right click caused menu to open. How should I do this here?
I am using MVVM Avalonia, but I hope in WPF it's working the same way so that I could add WPF hashtag to this question.

You don't need to add ContextMenu to ItemsControl. It needs to be added to the items ItemsControl.
This is done in ItemContainerStyle.
In such a case, in ContextMenu DataContext will contain the element on which the ContextMenu is called.
Example:
<Window.Resources>
<spec:StringCollection x:Key="strings">
<sys:String>First</sys:String>
<sys:String>Second</sys:String>
<sys:String>Third</sys:String>
</spec:StringCollection>
<Style x:Key="ItemStyle" TargetType="ContentPresenter">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<TextBlock Text="{Binding}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<ItemsControl ItemsSource="{StaticResource strings}"
ItemContainerStyle="{StaticResource ItemStyle}"/>

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 set ItemTemplate and Template for contexmenu along with data binding for ItemTemplate

On click of toggle button I want to show a context menu. As I want to change the visual appearance of context menu, I have created controltemplate and data template as below,
<TabItem>
<TabItem.HeaderTemplate>
<DataTemplate>
<ContextMenu ItemSource="{Binding Collection}">
<ContextMenu.Template>
<ControlTemplate>
<Grid Margin="10">
<ListBox Width="150" Height="70"/>
</Grid>
</ControlTemplate>
<ContextMenu.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox Content="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu.Template>
</ContextMenu>
</DataTemplate>
</TabItem.HeaderTemplate>
</TabItem>
Post this change I could see only a LisBox being shown clicking upon toggle button. I am not able to visualize the Data template that I had set (Checkbox). Data binding is also not working. Not able to figure out what could be the issue.
You should bind the ItemsSource property of the ListBox to the ItemsSource of the ContextMenu and define an ItemTemplate for the ListBox:
<ContextMenu ItemsSource="{Binding Collection}">
<ContextMenu.Template>
<ControlTemplate TargetType="ContextMenu">
<Grid Margin="10">
<ListBox ItemsSource="{TemplateBinding ItemsSource}" Width="150" Height="70">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</ControlTemplate>
</ContextMenu.Template>
</ContextMenu>

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.

Create separate context menu for listview and for listview item

I have working context menu for listview control in my WPF app. I'd like to have context menu items enabled if user right clicks on listview item but disabled them is click occurs on panel area of listview.
Thanks
MK
Update: this is the my list view that works but I'd like to disable MenuItems "Remove" and "Calculate" when user clicks on panel area. Thanks for responding
<ListView Name="lb_proplist" DisplayMemberPath ="Name" HorizontalAlignment="Left" ToolTip="Use right click to see more options"
ItemsSource="{Binding Converter={StaticResource FilterByPropTypeConverter}}" Margin="0,0,0,0"
ContextMenuOpening="ContextMenu_ContextMenuOpening" >
<ListView.ContextMenu>
<ContextMenu >
<MenuItem Name="cmi_addNew" Header="Add New"
Command="{Binding AddNewItemItem}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<MenuItem Name="cmi_remove" Header="Remove"
Command="{Binding RemoveItem}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu},
Path=PlacementTarget.SelectedItem}" />
<MenuItem Name="cmi_calculate" Header="Calculate"
Command="{Binding CalculateItem}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu},
Path=PlacementTarget.SelectedItem}" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="200"/>
</GridView>
</ListView.View>
</ListView>
Well, this can be done using ItemsTemplate property for ListView:
<ListView.ItemTemplate>
<DataTemplate>
<DockPanel Tag = "{Binding DataContext, ElementName=myListView}">
<TextBlock Text="{Binding}"/>
<DockPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Local Item 1"
Command="{Binding Path=PlacementTarget.Tag.CommandName, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
<MenuItem Header="Local Item 2"/>
</ContextMenu>
</DockPanel.ContextMenu>
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
In example, ContextMenu is created only for items, but not for whole control.
Also, Tag is added into DockPanel to access original DataContext.
Well, in case of GridView you need to override style for ListViewItem, and bind ContextMenu as StaticResource.
<ListView.Resources>
<ContextMenu x:Key="ItemContextMenu">
<MenuItem Header="Add New"/>
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource ItemContextMenu}"/>
</Style>
</ListView.ItemContainerStyle>
Please, take a look at this article and answer.

WPF usercontrol command binding to window viewmodel

I have a Window which contains UserControl1 and UserControl2. These user controls have their own viewmodels. Also, these user controls use UserControl3 to display data. So, when UserControl1 uses UserControl3 the UserControl3 has the same viewmodel as UserControl1.
I have a binding in UserControl3 which I wish to call the command which is on the viewmodel of UserControl1.
But I can't find a way to make it work. Any help is welcomed. Thank you very much.
Here is my binding which does not work:
<UserControl x:Class="MyNamespace.UserControl3"
xmlns:local="clr-namespace:MyNamespace">
<UserControl.Resources>
<DataTemplate DataType="{x:Type g:GraphNode}">
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="My Command" Command="{Binding Path=DataContext.MyCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:UserControl3}}}"/>
</ContextMenu>
</StackPanel.ContextMenu>
<Grid>
<ContentControl Content="{Binding Data}"/>
</Grid>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
This works in my app:
<DataTemplate DataType="{x:Type g:GraphNode}">
<StackPanel Tag="{Binding}">
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="My Command" Command="{Binding Path=PlacementTarget.Tag.MyCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}" />
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
The key is that ContextMenus are on a different window, so you can't access datacontexts like you usually do.
You will have to adapt this so that the object containing the Command you seek is set as the Tag of the StackPanel (which is the PlacementTarget of your ContextMenu).

Resources