WPF ContextMenu itemtemplate, menuitem inside menuitem - c#

I have the following xaml:
<ContextMenu ItemsSource="{Binding TestItems}">
<ContextMenu.ItemTemplate>
<DataTemplate DataType="models:TestItemModel">
<MenuItem IsChecked="{Binding IsSelected}" Header="{Binding Header}" />
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
The TestItemModel class only consists of a IsSelected boolean property and a Header string property.
TestItems is a list of TestItemModels.
The data is binded to the contextmenu but it is reflected in the UI as a MenuItem inside a MenuItem (with the additional margins as such, making the menu very big). I can fix this by changing the MenuItem inside the DataTemplate to a TextBox, but then I cannot bind the IsSelected anymore (which I need for visualization properties).
There are a couple of questions I have regarding this:
Why is there a MenuItem inside a MenuItem? This doesn't make sense to me as it's not binded to a menuitem list but to a list of TestItemModels.
How can I resolve this?

Because MenuItem is the container type and when it translates your view model into visual item it will wrap your template in MenuItem. In the same way ListBox will create ListBoxItem or ListView will use ListViewItem. To bind properties of the wrapper you need to use ItemContainerStyle
<ContextMenu ItemsSource="{Binding TestItems}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="IsChecked" Value="{Binding IsSelected}"/>
<Setter Property="Header" Value="{Binding Header}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
or, if you prefer, you can do it partially with ItemTemplate and ItemContainerStyle
<ContextMenu ItemsSource="{Binding TestItems}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</ContextMenu.ItemTemplate>
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="IsChecked" Value="{Binding IsSelected}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
In this scenario whatever is in ItemTemplate will become MenuItem.Header but IsChecked property still needs to be bound in ItemContainerStyle

Related

WPF: ContextMenu in templated ListBox Item (InvalidCastException)

I'm trying to add a context menu to a ListBoxItem. I'm using ListBox.ItemTemplate and DataTemplate (with a Grid) to define the layout of the item and the ListBoxItem is styled.
In a search that this should be the way to go:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Rename" Click="Rename_Click" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
But this throws an XamlParseException/InvalidCastException saying
Couldn't cast an object of the type
System.Windows.Controls.MenuItem to the type
System.Windows.Controls.Grid
I tried adding the context menu to the Grid in the ItemTemplate, but then it only works when you click on one of the elements in the Grid (there is some empty space) (or if I add a background to the Grid, but that overrides/"covers" the styling, for hover & selected, of the Item itself)
I can't find any similar issues when searching, and I can't figure out the logic of the Exception..
You could overcome this by defining the ContextMenu as a resource:
<ListBox>
<ListBox.Resources>
<ContextMenu x:Key="cm">
<MenuItem Header="Rename" Click="Rename_Click" />
</ContextMenu>
</ListBox.Resources>
<ListBox.ItemTemplate>
...
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu" Value="{StaticResource cm}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Use the TargetType="ListBoxItem" string of code. Because the coding langauge needs to know the Listbox!

Get name(or index) of selected menu item from context menu, which was dynamically generated via ItemsSource bound to a ObservableCollection

I have a context menu that contains 1 menu item. That menu item is bound to a ObservableCollection for the itemssource.
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Example Menu Item"
Command="{Binding Path=DataContext.ExampleCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListView}}"
ItemsSource="{Binding ObservableItems}">
</MenuItem>
</ContextMenu>
</ListView.ContextMenu>
How do I get the name (or index) of the menu item that was selected. The problem is I cannot bind a command to each individual menu item, as they are dynamically generated.
For example how would I know which item was clicked, as seen in the image below?
Any help is much appreciated. Thanks.
You still can bind Command and CommandParameter per item for dynamically generated lists but you need to use ItemContainerStyle
<ContextMenu>
<MenuItem Header="Example Menu Item" ItemsSource="{Binding ObservableItems}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Path=DataContext.ExampleCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListView}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</ContextMenu>
in this example CommandParameter, which is passed to you ExampleCommand command as parameter, will be an item in your collection (current DataContext of child item)
EDIT
To get index you can use pair of ItemsControl properties: AlternationCount and AlternationIndex. You set AlternationCount to number of items in your collection and pass AlternationIndex to your command
<MenuItem Header="Example Menu Item" ItemsSource="{Binding ObservableItems}" AlternationCount="{Binding ObservableItems.Count}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding ...}"/>
<Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource Self}, Path=(ItemsControl.AlternationIndex)}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>

Add a separator to a Context Menu bound to models in WPF with MVVM Light

I have a context menu in WPF with the following constraints:
Menu items should be bound to a list of models
The menu may contain separators
Menu items can have sub menu items
Menu items may be turned off based on state
The order of the menu items may not change (i.e. menu item 1 must appear above menu item 2, if present)
I have seen solutions where the view model contains a list of controls, however, this is not an acceptable solution.
The approach I've taken almost works.
<ContextMenu DataContext="{Binding Data.ContextMenuViewModel, Source={StaticResource proxy}}"
ItemsSource="{Binding Data.ContextMenuViewModel.MenuItems, Source={StaticResource proxy}}"
Visibility="{Binding CellContextMenuOpen, Converter={StaticResource BoolToVisibilityConverter}}" >
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="CommandParameter" Value="{Binding CommandParameter}" />
<Setter Property="ItemsSource" Value="{Binding SubMenuItems}" />
<Setter Property="IsCheckable" Value="{Binding IsCheckable, FallbackValue=False}" />
<Setter Property="IsChecked" Value="{Binding IsChecked, FallbackValue=False}" />
</Style>
</ContextMenu.ItemContainerStyle>
<ContextMenu.ItemTemplateSelector>
<ccm:MenuItemTemplateSelector>
<ccm:MenuItemTemplateSelector.SeparatorTemplate>
<DataTemplate>
<Separator HorizontalAlignment="Stretch" IsEnabled="False" Margin="0" />
</DataTemplate>
</ccm:MenuItemTemplateSelector.SeparatorTemplate>
<ccm:MenuItemTemplateSelector.MenuItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</ccm:MenuItemTemplateSelector.MenuItemTemplate>
</ccm:MenuItemTemplateSelector>
</ContextMenu.ItemTemplateSelector>
However, I've discovered that some properties in ItemsContainerStyle and the DataTemplate are mutually exclusive. For example, if I were to add a visibility binding to the items container style, the data template wont be applied at all.
The problem with the solution above is that the MenuItem itself wont collapse based on a binding, I can only get the header to collapse. If I did not need the separators, I could put everything in an ItemContainerStyle and be done with it.
Any suggestions would be greatly appreciated.

Indexing WPF tab controls

I have bound a tab control to a collection via a ViewModel property. I want the tab headers to reflect the index of the item it is showing, but this seems to be inordinately difficult. Is there an easy way to do this?
<TabControl
Style ="{StaticResource GtlTabControl}"
ItemsSource="{Binding Images}"
SelectedIndex="{Binding CurrentImageIndex}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="???"/>
</Style>
</TabControl.ItemContainerStyle>
...
</TabControl>
You can use an AlternationIndex inside the xaml. Something like this:
<TabControl Style ="{StaticResource GtlTabControl}"
ItemsSource="{Binding Images}"
SelectedIndex="{Binding CurrentImageIndex}"
AlternationCount="{Binding Path=Items.Count, RelativeSource={RelativeSource Self}}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Path=(ItemsControl.AlternationIndex), RelativeSource={RelativeSource FindAncestor, AncestorType=TabItem}}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
There are also alternatives posted here: Show SelectedIndex in WPF Tabcontrol header template
If "Images" is a property of type List<MyImage>, then you can have an "Id" property in class "MyImage" and use it to bind to "Header"
public class MyImage{
public int Id{get;set;}
}
....<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Path=Id}"/>
</Style>
</TabControl.ItemContainerStyle>

Ancestor level binding not working in MenuItem command

We have use the hierarchical template to populate the menuitem
<UserControl.DataContext>
<local:MenuViewModel/>
</UserControl.DataContext>
<Grid>
<!--Initialize the Menu-->
<Menu Name="Part_Menu" ItemsSource="{Binding MenuCollection}" Background="#E5E5E5" VerticalAlignment="Center">
<Menu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding MenuItemCollection}">
<TextBlock Text="{Binding Header}" />
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="CommandParameter" Value="{Binding Header}"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Command"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MenuViewModel}, AncestorLevel=2,Mode=FindAncestor},Path=MenuClick}"></Setter>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
</Grid>
In this i have tried to bind the MenuClick(ICommand) to MenuItem , but it did not bind correctly
I have check the binding in the following forum link
[http://stackoverflow.com/questions/23941314/wpf-how-can-i-create-menu-and-submenus-using-binding?rq=1][1]
In this command added in the MenuModel , i need to Command in the MenuViewmodel
This way of binding :
{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MenuViewModel},
AncestorLevel=2, Mode=FindAncestor}
..is not working, because the AncestorType does not derive from UIElement.
The Path of the binding should be DataContext.MenuClick, and the AncestorType should be Menu. Putting it all together :
<Setter Property="Command"
Value="{Binding Path=DataContext.MenuClick,
RelativeSource={RelativeSource AncestorType={x:Type Menu},
AncestorLevel=2}}">
</Setter>
Mode=FindAncestor is the default mode, so i left that out.
In the MSDN: RelativeSource.AncestorType Documentation it is only stated that any Type could theoretically be used, however, FindAncestor inspects the visual tree to try and find the given ancestor, so whatever type you are looking for must be present in the visual tree. Hope this helps.

Categories

Resources