Keyboard navigation no longer works in WPF menu with templates - wpf

In a WPF menu that is data bound to a collection, I can style everything correctly, but navigation with the keyboard no longer works as expected.
Consider the XAML below (you can paste it in a tool like KaXaml).
Two things:
Opening a menu and navigating to the right with the arrow keys will by default select the first item in the opened menu item. For example:
Click on 'One'
Press right arrow --> Menu two opens but nothing is selected
Press right arrow again --> Menu three opens and first item is selected
Navigating the subitems doesn't work. When pressing the right arrow, the next top-level menu item is opened, instead of drilling down into the subitem
Click on 'Two'
Press left arrow
Press right arrow --> We drill down, selecting One > A > I
Press right arrow --> There's nothing to drill down into, so we move on to MenuItem Two
Press right arrow --> Instead of drilling down into Two > 'Bound property' > 'Another bound property', we move on to top-level MenuItem 'Three'
How can I ensure the 'default' menu behavior when navigating with the keys?
This is the XAML you can test it with:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Menu VerticalAlignment="Top">
<MenuItem Header="One">
<MenuItem Header="A">
<MenuItem Header="I" />
<MenuItem Header="II" />
<MenuItem Header="III" />
</MenuItem>
<MenuItem Header="B"/>
<MenuItem Header="C"/>
</MenuItem>
<MenuItem Header="Two">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<MenuItem>
<MenuItem.HeaderTemplate>
<DataTemplate>
<TextBlock>
<Run Text="Bound property" />
</TextBlock>
</DataTemplate>
</MenuItem.HeaderTemplate>
<MenuItem Header="Something"/>
<MenuItem Header="Something else"/>
</MenuItem>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</MenuItem.ItemContainerStyle>
<MenuItem Header="A"/>
<MenuItem Header="B"/>
<MenuItem Header="C"/>
</MenuItem>
<MenuItem Header="Three">
<MenuItem Header="A">
<MenuItem Header="I" />
<MenuItem Header="II" />
<MenuItem Header="III" />
</MenuItem>
<MenuItem Header="B"/>
<MenuItem Header="C"/>
</MenuItem>
</Menu>
</Grid>
</Page>
Update
The special thing is that the sub MenuItems in my ControlTemplate should be added for each DataBound MenuItem. This is because I have an ObservableCollection with which I will build up the MenuItems inside MenuItem "Two". For each of these MenuItems, I need the same sub MenuItems. They will be bound to a Command that is the same for each of the MenuItems, except for the CommandParameter.
So what I want in the end is:
Two
Bound property 1
Something
Something else
Bound property 2
Something
Something else
Bound property 3
Something
Something else

You should not create MenuItem elements inside the template of your MenuItem. You should style your items so they have the bound content.
I've added XML as a source for your menu items, so "Two" item has content bound to XML (you can replace it with your collection). For this solution you should have a collection for your submenu items (one for all top items, not per a single top item). The keyboard navigation works correct in the example below.
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<XmlDataProvider x:Key="MenuProvider" XPath="Items">
<x:XData>
<Items xmlns="">
<Item Title="Bound property 1" Parameter="1" />
<Item Title="Bound property 2" Parameter="2" />
<Item Title="Bound property 3" Parameter="3" />
</Items>
</x:XData>
</XmlDataProvider>
<XmlDataProvider x:Key="SubMenuProvider" XPath="Items">
<x:XData>
<Items xmlns="">
<Item Title="Something" />
<Item Title="Something else" />
</Items>
</x:XData>
</XmlDataProvider>
</Page.Resources>
<Grid>
<Menu VerticalAlignment="Top">
<MenuItem Header="One">
<MenuItem Header="A">
<MenuItem Header="I" />
<MenuItem Header="II" />
<MenuItem Header="III" />
</MenuItem>
<MenuItem Header="B" />
<MenuItem Header="C" />
</MenuItem>
<MenuItem Header="Two" ItemsSource="{Binding Source={StaticResource MenuProvider}, XPath=*}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding XPath=#Title}" />
<Setter Property="ItemsSource" Value="{Binding Source={StaticResource SubMenuProvider}, XPath=*}" />
<Setter Property="Tag" Value="{Binding XPath=#Parameter}" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding XPath=#Title}" />
<Setter Property="CommandParameter" Value="{Binding Path=Tag, RelativeSource={RelativeSource AncestorType=MenuItem}}" />
</Style>
</Setter.Value>
</Setter>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
<MenuItem Header="Three">
<MenuItem Header="A">
<MenuItem Header="I" />
<MenuItem Header="II" />
<MenuItem Header="III" />
</MenuItem>
<MenuItem Header="B" />
<MenuItem Header="C" />
</MenuItem>
</Menu>
</Grid>
</Page>

I did restyle the menu item Two while keeping your desired needs
<MenuItem Header="Two">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header"
Value="Bound property" />
<Setter Property="ItemsSource">
<Setter.Value>
<!--binding sub menu items to a collection-->
<x:ArrayExtension Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>Something</sys:String>
<sys:String>Something else</sys:String>
</x:ArrayExtension>
</Setter.Value>
</Setter>
</Style>
</MenuItem.ItemContainerStyle>
<MenuItem />
<MenuItem />
<MenuItem />
</MenuItem>
example above solves the navigation issue while keeping the binding to child items as desired.
give it a try and see how close it is.

Related

WPF: Adding a Separator to a Context Menu defined in Resources

I have a ContextMenu that is being defined in Resources. To be more specific, this context menu is used for a DataGrid, and is to appear when a row is being right-clicked. Because of that, I cannot use <DataGrid.ContextMenu> to define this context menu, I needed to do this:
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" BasedOn="{StaticResource {x:Type DataGridRow}}">
<Setter Property="ContextMenu" Value="{StaticResource DataGridCM}" />
</Style>
</DataGrid.RowStyle>
<ContextMenu x:Key="DataGridCM"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.DataContext}">
<MenuItem Header="Item 1" Command="{Binding Command1}" />
<MenuItem Header="Item 2" Command="{Binding Command2}" />
<Separator />
<MenuItem Header="Other Item" Command="{Binding OtherCommand}" />
</ContextMenu>
This gave me this error:
A style intended for type 'MenuItem' cannot be applied to type 'Separator'.
Is there any method to add the Separator in? I have tried ItemsSource shown below but it still gave me the same runtime error.
<ContextMenu.ItemsSource>
<CompositeCollection>
<MenuItem Header="Item 1" Command="{Binding Command1}" />
<MenuItem Header="Item 2" Command="{Binding Command2}" />
<Separator />
<MenuItem Header="Other Item" Command="{Binding OtherCommand}" />
</CompositeCollection>
</ContextMenu.ItemsSource>
I know this can be done if I use code-behind, but I would want to avoid that because I am using MVVM. I do not wish that other people taking over my project years later would get confused by a single View class with code-behind. Is there any way to solve this probably purely from XAML level?

Add a descriptive MenuItem to a WPF ContextMenu

I am trying to add an item to a WPF-ContextMenu that is only used to "describe" the items below but I am not sure how to add a simple line of text above all the items, where the text is centrally aligned and the text is not selectable like the normal MenuItems.
I tried something like this:
<ContextMenu Grid.Row="0" StaysOpen="False">
<TextBlock Text="Add New:" IsEnabled="False" HorizontalAlignment="Center"/>
<MenuItem Header="one"/>
<MenuItem Header="two (horizontal)"/>
<MenuItem Header="two (vertical)"/>
<MenuItem Header="three"/>
<MenuItem Header="four"/>
<MenuItem Header="six"/>
</ContextMenu>
but unfortunately the TextBlock is neither aligned centrally, nor is it unselectable.
The problem is that using a MenuItem and setting the IsEnabled-property to false, the text is not normal black anymore and also I can't really align it centrally.
Hopefully, someone can think of a easy solution here, I simply couldn't find anything.
I will suggest you to create the SubMenu items for menuitems. In this way menuitems will group all the submenuitems under it.
I had given the answer to create the context menu with menuitem and submenuitems purely using mvvm. Here you can refer to it.
WPF Context Menu with dropdown list showing hyperlinks
OR For the case you mentioned. I tried this:
<ContextMenu StaysOpen="False">
<MenuItem Header="Add New:" IsEnabled="False" HorizontalAlignment="Center">
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="Black"/>
</Trigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
<MenuItem Header="one"/>
<MenuItem Header="two (horizontal)"/>
<MenuItem Header="two (vertical)"/>
<MenuItem Header="three"/>
<MenuItem Header="four"/>
<MenuItem Header="six"/>
</ContextMenu>
and I got menu like
Add New: is non-selectable and also mouse over does not highlight it.
Hope it helps. Thanks

MenuItem with subitems binded to Commands. (MVVM)

Have a code like:
<MenuItem ItemSource="SOURCE">
..sub MenuItems
</MenuItem>
How to create the right template that allows to bind the each subitem to the save command.
Something like that:
<MenuItem ItemsSource="{Binding SubItems}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding SaveCommand}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
This is how I did it in a project:
In the Window definition i "define" the commands workspace.
<Window x:Class="WorkForce.Views.MainWindow"
...
xmlns:commands="clr-namespace:WorkForce.Commands"
...
>
After that I connect them to each menu-item.
<MenuItem Header="_File">
<MenuItem Header="_New..." Command="commands:MainWindowCommands.NewFile"/>
<MenuItem Header="_Open..." Command="commands:MainWindowCommands.OpenFile"/>
<MenuItem Header="_Save..." Command="commands:MainWindowCommands.SaveFile"/>
</MenuItem>
I hope that is enough
I saw that you want to add it dynamically. Please look at this: WPF: How can you add a new menuitem to a menu at runtime?

How to get the Selected Item in Context Menu

I have a context menu that has binding items and I want to set up a command and command parameter so I know which item was clicked on but I don't know how.
<MenuItem Command="{Binding Sync}"
Header="Synchronize"
ItemsSource="{Binding ItemsToSync}">
<MenuItem.Icon>
<Image Height="25" Source="Sync.png" />
</MenuItem.Icon>
</MenuItem>
You can try something like this:
In this example I have a listview and I can right click and delete a selected item. The Reason I'm using RelativeSource here is because when it comes to passing parameters in menuitems, most of the time at this level you can't reach the datacontext of the page. Hope this helps.
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete" Command="{Binding Path=DeleteDescriptions}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}" Name="MenuItem1">
</MenuItem>
</ContextMenu>
</ListView.ContextMenu>
That did not help but I was able to create my own solution.
<MenuItem Header="Synchronize" ItemsSource="{Binding ItemsToSync}">
<MenuItem.Icon>
<Image Height="25" Source="Sync.png" />
</MenuItem.Icon>
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Header" Value="{Binding Name}" />
<Setter Property="MenuItem.IsChecked" Value="{Binding IsCurrent}" />
<Setter Property="MenuItem.Command" Value="PT:Commands.SyncFromContextMenu" />
<Setter Property="MenuItem.CommandParameter" Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</MenuItem>
I did have to create a static class for the command.

Align menu in wpf

I have a vertical menu set to the left side of the window. Its items open just on (above) it and this prevents the user from having a full view of the menu when an item is open.
I want each element to open just on the right of the menu, so as to have an entire view of both the rest of menu and the opened elements. How can this be done? May be with the aid of transforms or triggers?
Here is some code:
<MenuItem Header="Maths">
<MenuItem Background="LightGray" Header="Add"/>
<MenuItem Background="LightGray" Header="Subtract"/>
<MenuItem Background="LightGray" Header="Multiply"/>
<MenuItem Background="LightGray" Header="Divide"/>
</MenuItem>
So just to be clear, the MenuItem 'Maths' above is in a WPF Menu and you have changed that Menu's ItemsPanel to be a Vertical StackPanel or something so 'Maths' is above/below other sibling menu items. If so what is happening is that the default template for MenuItem's whose role is TopLevelHeader (MenuItem that has child Items and is directly within a Menu) is such that the popup is below (or above) the menu item. You will probably want to retemplate those menu items. On hacky (and ugly alternative) is to use the template that would be used for SubmenuHeader role menu items (i.e. a MenuItem that has child Items and is within another MenuItem). e.g.
<Menu HorizontalAlignment="Left">
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<Menu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Style.Triggers>
<Trigger Property="Role" Value="TopLevelHeader">
<Setter Property="Template" Value="{DynamicResource {x:Static MenuItem.SubmenuHeaderTemplateKey}}" />
</Trigger>
<Trigger Property="Role" Value="TopLevelItem">
<Setter Property="Template" Value="{DynamicResource {x:Static MenuItem.SubmenuItemTemplateKey}}" />
</Trigger>
</Style.Triggers>
</Style>
</Menu.ItemContainerStyle>
<MenuItem Header="Just Item" />
<MenuItem Header="Maths">
<MenuItem Header="Add" />
<MenuItem Header="Subtract" />
</MenuItem>
<MenuItem Header="Misc">
<MenuItem Header="Other" />
</MenuItem>
</Menu>

Resources