WPF Submenu for context menu - c#

If the Context menu is "hardcoded" in xaml, then it is easy to add submenus. For example:
<ContextMenu>
<MenuItem Header="Comm1" Command="{Binding Blabla1}">
<MenuItem Header="SubComm1" Command="{Binding Blabla2}"></MenuItem>
</MenuItem>
<MenuItem Command="Comm2"></MenuItem>
<MenuItem Command="Comm3"></MenuItem>
</ContextMenu>
This means, that the ContextMenu has three elements (Comm1, Comm2 and Comm3) and Comm1 has submenu SubComm1.
I have made my ContextMenu a bit more flexible:
<ContextMenu ItemsSource="{Binding ContextMenuItemsSource}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding ContextMenuCommandHeader}"></Setter>
<Setter Property="Command" Value="{Binding ContextMenuCommand}"></Setter>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
Basically I can have any number of elements in ContextMenu, and any element can have any Command. How can I add submenu to ContextMenu element?

You can set MenuItem.ItemsSource to the nested collection. This will generate the submenu for any menuitem. For this the model backing your MenuItem should have submenuitems collection in it
<ContextMenu ItemsSource="{Binding ContextMenuItemsSource}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="ItemsSource" Value="{Binding ContextMenuSubItems}"></Setter>
<Setter Property="Header" Value="{Binding ContextMenuCommandHeader}"></Setter>
<Setter Property="Command" Value="{Binding ContextMenuCommand}"></Setter>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
Similarly you can set the MenuItem.ItemContainerStyle and MenuItem.ItemTemplate to style your submenuitems.

Specifying explicitly, without binding a collection you can add sub menus by nesting them.
A MenuItem can have other MenuItem elements within it as child/sub menus and can go up to several levels. The following code adds three children menu items to first menu item.
<MenuItem Header="_File">
<MenuItem Header="_Open" IsCheckable="true" />
<MenuItem Header="_Close" IsCheckable="true" />
<MenuItem Header="_Save" IsCheckable="true" />
</MenuItem>
Source: https://www.c-sharpcorner.com/uploadfile/mahesh/menus-in-wpf/

Related

C# WPF - How to create submenu form BindingList without overwriting MenuItem style?

I'm trying to write just some sample application using MVVM. In my view, I have two MenuItems with submenus
<Menu >
<--!first submenu-->
<MenuItem Header="{Binding AppViewModel.AppSubtitles[top_menu_navigation]}" >
<MenuItem Header="{Binding AppViewModel.AppSubtitles[top_menu_go_forward]}" Command="{Binding NavigateForward}" />
<MenuItem Header="{Binding AppViewModel.AppSubtitles[top_menu_go_back]}" Command="{Binding NavigateBack}" />
</MenuItem>
<--!second submenu-->
<MenuItem ItemsSource="{Binding AppViewModel.AllLanguagesItems}" Header="{Binding AppViewModel.AppSubtitles[top_menu_language]}" >
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" >
<Setter Property="Header" Value="{Binding DisplayName}"/>
<Setter Property="Command" Value="{Binding ChangeLanguage}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
(items in AllLanguagesItems list have properties DisplayName and ChangeLanguage)
I also dynamically change application style by adding/removing ResourceDictionaries (with styles for buttons, menuitems etc.) to/from application MergedDictionaries.
The problem is that changing styles works well with first submenu, menuitems styles are updated correctly, but in second submenu, only partially - mainly, OnHover effect doesn't change.
Before adding dictionary
First submenu beforeSecond submenu before
After adding dictionary
First submenu afterSecond submenu after
I assume it has something to do with overwriting styles, but how would you define bindings otherwise?
I tried
<MenuItem ItemsSource="{Binding AppViewModel.AllLanguagesItems}" Header="{Binding AppViewModel.AppSubtitles[top_menu_language]}" >
<MenuItem.ItemTemplate>
<DataTemplate >
<MenuItem Header="{Binding DisplayName}" MaxWidth="200" MaxHeight="20"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
but it just creates MenuItem inside MenuItem
MenuItem inside MenuItem
EDIT:
Styles: https://github.com/Kaszub09/MVVM-WPF-DI-Sample/tree/master/WPFSampleApplication/ApplicationData/Themes
Use the BasedOn property in your style

WPF: MenuItem icon in the wrong place when using data template

I have the following code:
<Menu>
<MenuItem ItemsSource="{Binding SomethingMenuItems}" Header="Something"/>
</Menu>
Where MenuItems is a collection of objects of type SomethingMenuItem.
I also have:
<DataTemplate DataType="{x:Type SomethingMenuItem}">
<MenuItem Header="{Binding OrderTypeName}">
<MenuItem.Icon>
<Image Source="{Binding IconName}"/>
</MenuItem.Icon>
</MenuItem>
</DataTemplate>
I'd expect to get (I get something like this when I hardcode the menu items):
What I get instead is:
What am I doing wrong?
You might want to use ItemContainerStyle instead of DataTemplate. You have to style the container of the data item than just providing a template for data item.
With your DataTemplate, you basically displaying another nested MenuItem as content for each MenuItem generated for your Menu Something, and your inner MenuItem has the image in the correct place. I am attaching VisualTree from Snoop here for your reference.
Below is the Style for the container of the data item (in this case a MenuItem):
<MenuItem ItemsSource="{Binding SomethingMenuItems}"
Header="Something">
<MenuItem.Resources>
<Image Source="{Binding IconPath}" x:Key="IconImage" x:Shared="False"/>
<Style TargetType="MenuItem" >
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="Icon" Value="{StaticResource IconImage}" />
</Style>
</MenuItem.Resources>
</MenuItem>
You can see no nested MenuItems when you apply the above style, have added image here
With the above style applied, this is how the Menu looks:
Refer to this MSDN page to know more about ItemContainerStyle.
So, sthotakura's answer set me on the right track, but the code he posted didn't quite work because the style got applied to the parent menuitem as well.
In case someone else has a similar problem and stumbles on this question, here's the code that works:
<MenuItem ItemsSource="{Binding SomethingMenuItems}"
Header="Something">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem" >
<Style.Resources>
<Image Source="{Binding IconPath}" x:Key="IconImage" x:Shared="False"/>
</Style.Resources>
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="Icon" Value="{StaticResource IconImage}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>

Bind Items to MenuItem -> use Command

I have a MenuItem, which has a collection of items in it. It looks like the File -> Open Menuitem.
So:
File
Open
Open from DataBase
File 1
File 2
File 3
XAML Code:
<Menu>
<MenuItem Header="File">
<MenuItem Header="Open">
<MenuItem Header="From Database" ItemsSource="{Binding OCFragebogen}"/>
</MenuItem>
</MenuItem>
</Menu>
I want to call a Command, when a specific item has been clicked. Example: User clicks on File 1, a command should be called where the "File 1" is the Command Parameter.
ViewModel contains the Items, which I want to display in the MenuItem "collection"
private ObservableCollection<string> _OCFragebogen;
public ObservableCollection<string> OCFragebogen
{
get
{
if (_OCFragebogen == null)
_OCFragebogen = new ObservableCollection<string>();
return _OCFragebogen;
}
set
{
_OCFragebogen = value;
RaisePropertyChanged(() => OCFragebogen);
}
}
To make it clear: When the user clicks on an item (from the ItemsSource) in the MenuItem, a Command should be called where I want to do something with the clicked Item.
Edit: Where do I have to use the command to call a method (RelayCommand) in my ViewModel? I want it to be used when an Item from the ItemsSource has been clicked + I want to pass the clicked item to the method.
This should work for you
<MenuItem Header="From Database"
ItemsSource="{Binding YourItemSource}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MenuItem}, Path=DataContext.YourCommandName}"></Setter>
<Setter Property="CommandParameter" Value="{Binding}"></Setter>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
Try to change its ItemContainerStyle and then bind the command from ItemsSource item,
<MenuItem Header="From Database" ItemsSource="{Binding OCFragebogen}"
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding YourCommand}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
I haven't tried if it works just a guess
edited answer
<MenuItem Header="From Database" ItemsSource="{Binding OCFragebogen}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MenuItem}}, Path=DataContext.YourCommand }" />
<Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource Self}, Path=Header}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>

Loop xaml element

I have an array of strings. For each of these strings, I'd like to create a seperate xaml element (<menuitem> is from an external library):
<MenuItem Header="Update">
<MenuItem Header="arrayvalue1" Command="{Binding UpdateCommand}" />
<MenuItem Header="arrayvalue2" Command="{Binding UpdateCommand}" />
<MenuItem Header="arrayvalue3" Command="{Binding UpdateCommand}" />
</MenuItem>
Instead of hardcoding 3 elements, I'd like to create these from the array of strings.
Is this possible and if so, how?
MenuItem is an ItemsControl, so you can bind any collection to the ItemsSource property and it will generate the children for you. In the case of MenuItem, the children generated are also MenuItems. To apply bound values to properties on those children you can set an ItemContainerStyle which will be applied to each. Since the Command you want to use is on the top level DataContext you will need to use more of an indirect binding, which may be different depending on which technology you're using. Here's how it looks for WPF:
<MenuItem Header="Update" ItemsSource="{Binding Strings}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Path=DataContext.UpdateCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Menu}}}" />
<Setter Property="Header" Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
What you're looking for is called an ItemsControl. You can use it to present a bunch of items in whatever form you like by adding an ItemTemplate to it.

"No target found for method" thrown by Caliburn Message.Attach()

I have a list box for which I am styling ItemContainer to include a context menu. Here is the xaml for the same.
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
...
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Remove Group" cal:Message.Attach="DeleteGroup"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
I have coded the target method in ViewModel as given below.
public void DeleteGroup() { //ToDo
...
}
The ViewModel is set as the DataContext of the UserControl in which there is the ListBox.
The above code results in "no target found for method". I am not sure why this doesn't work. I have also tried the following variation
<MenuItem Header="Remove Group" cal:Message.Attach="DeleteGroup"
cal:Action.Target="{Binding ElementName=UCRelayDispositionView, Path=DataContext}">
where UCRelayDispositionView is the name of the UserControl.
Why does the above code do not work?
Edit: 1
Also tried the following
<MenuItem Header="Remove Group" cal:Message.Attach="DeleteGroup"
cal:Action.TargetWithoutContext="{Binding ElementName=UCRelayDispositionView, Path=DataContext}">
and this
<MenuItem Header="Remove Group" cal:Message.Attach="DeleteGroup"
cal:Action.TargetWithoutContext="{Binding ElementName=UCRelayDispositionView}">
EDIT: 2
I have tried to use the Tag in the following way on ItemContainer but it doesn't work either.
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Tag" Value="{Binding Path=DataContext, ElementName=UCRelayDispositionView}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Remove Group"
cal:Message.Attach="DeleteGroup()"
cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}"/>
</ContextMenu>
</Setter.Value>
</Style>
</ListBox.ItemContainerStyle>
EDIT 3: Binding Errors
System.Windows.Data Error: 40 : BindingExpression path error: 'PlacementTarget' property not found on 'object' ''MenuItem' (Name='')'. BindingExpression:Path=PlacementTarget.Tag; DataItem='MenuItem' (Name=''); target element is 'MenuItem' (Name=''); target property is 'TargetWithoutContext' (type 'Object')
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=UCRelayDispositionView'. BindingExpression:Path=DataContext; DataItem=null; target element is 'ContextMenu' (Name=''); target property is 'Tag' (type 'Object')
Your problem lies in that you are trying to bind the target to an element which doesn't exist in the same visual tree e.g. you have a ContextMenu on which the item resides.
To correctly get an action target, you need to use the ContextMenus PlacementTarget property.
Check out the following answer on SO for the XAML
WPF Context Menus in Caliburn Micro
So the following XAML should work:
<MenuItem Header="Blah" cal:Message.Attach="SomeMethod()" cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}">
This should look for the PlacementTarget on the ContextMenu and set the target for the action to the value of PlacementTarget.Tag (which should be the ListBoxItem).
If you set ListBoxItem.Tag (as you have already done) to be the DataContext of the parent container (the ListBox) you should be ok
so the tag binding should be:
<Setter Property="Tag" Value="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}}"/>
e.g. the whole thing should be:
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Tag" Value="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}">
<MenuItem Header="Remove Group" cal:Message.Attach="DeleteGroup()" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
Adding to Charleh's answer, if you're going to be using the same data context as the control, then you can just bind the DataContext instead of a Tag. Makes it a bit cleaner too.
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}" >
<MenuItem Header="Remove Group"
cal:Message.Attach="DeleteGroup()" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>

Categories