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

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.

Related

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

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

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 find datacontext of parent's parent for dynamic control

We have following xaml code to display context menu and fire command when delete menu is clicked.
For the context menu item, I would like to bind command which is present in DataContext of ItemsControl. We tried with RelativeSource={RelativeSource TemplatedParent} but as the tree is created dynamically (using dataContext) it is not able to find DeleteCommand.
<Grid x:Name="MyGrid" >
<ItemsControl ItemsSource="{Binding Path=TreeRoot.Children}">
<ItemsControl.ItemTemplate>
<HierarchicalDataTemplate>
<StackPanel>
<Label Background="Black" Content="{Binding Path=DisplayText}"/>
<TreeView ItemsSource="{Binding Converter={StaticResource myCompositeNodeConverter}}" >
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:CompositeNode}" ItemsSource="{Binding Path=Children}" >
<TextBlock Text="{Binding Path=DisplayText}" Foreground="Black"></TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:LeafNode}" ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=DisplayText}"></TextBlock>
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.ContextMenu>
<ContextMenu >
<MenuItem Header="Delete" Command="{Binding DeleteCommand}" />
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
</StackPanel>
</HierarchicalDataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
How do we bind DeleteCommand for context menu item to DataContext of ItemsControl?

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

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.

Resources