MenuCollection Binding twice MVVM + Databinding Child menu items - c#

As you can see in the image below, you can see 2 hover states. Here is the XAML
<Menu ItemsSource="{Binding Data.MenuCollection}">
<Menu.ItemTemplate >
<DataTemplate DataType="MenuItem">
<MenuItem Header="{Binding Header}" Command="{Binding Command}" ItemsSource="{Binding Children}"/>
</DataTemplate>
</Menu.ItemTemplate>
</Menu>
The Collection of data works on the header. However I can't get the Children nodes to appear.
public void CreateTempMenuList()
{
MenuCollection = new ObservableCollection<MenuItem>()
{
new MenuItem()
{
Header = "File",
Children = new ObservableCollection<MenuItem>()
{
new MenuItem()
{
Header = "Exit"
}
}
}
};
}
The MenuItem class is something I created. Each property has a setter that called the OnPropertiesChanged Function. I can add the class if needed, but I am pretty sure thats not the problem.
So my question is. How do i get rid of the 'double' hover. In the image you can see 2 borders. An outer border which i hover over. the hover stays until focused on something else.
My second question is how can i get the child items to work? The itemssource on the menuitem tag could be wrong but its all i could think of.

Define an HierarchicalDataTemplate:
<Menu ItemsSource="{Binding Data.MenuCollection}">
<Menu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.Resources>
<Menu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}" />
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
A System.Windows.Controls.MenuItem container is implicitly created for each item so you shouldn't add another MenuItem element in the template.
Also make sure that you don't bind to an ObservableCollection<System.Windows.Controls.MenuItem> because the ItemTemplate won't be applied to built-in MenuItem elements.

To make your current code work, right click your Menu control > Edit Additional Template > Edit ItemContainerStyle > Edit Copy.
And in the generated Style,
Search for this piece of code :
<Trigger Property="Role" Value="TopLevelItem">
<Setter Property="Padding" Value="7,2,8,3"/>
<Setter Property="Template" Value="{DynamicResource {ComponentResourceKey ResourceId=TopLevelItemTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}}"/>
</Trigger>
And change Padding to 0 instead of 7,2,8,3 .

Related

How to get the Content of selected Menu Item?

Summary of the problem
Goal
My goal is to create a Navigation menu in WPF.
The navigation items are built in the View Model using Nodes.
Selected SubItem of the menu will display the content of the selected Node.
Expected and actual result
This is what I have built so far:
Error
Now I need to present the selected MenuItem to ContentPresenter - and this is where I have the problem with.
What I have tried
This is my current code
XAML
<!--Menu-->
<Menu x:Name="NavigationTreeView" IsMainMenu="True" ItemsSource="{Binding Navigation}" Grid.Row="0">
<Menu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Path=Title}" />
<Setter Property="Template" Value="{StaticResource VsMenuTop}" />
</Style>
<HierarchicalDataTemplate DataType="{x:Type nav:PageViewModel}" ItemsSource="{Binding Children}" />
</Menu.Resources>
</Menu>
<!--Content-->
<ContentPresenter Grid.Row="1" Content="{Binding ElementName=NavigationTreeView, Path=SelectedItem.Content}" />
This will not work, because I am not able to bind it to Menu using this line here: SelectedItem.Content - and the Menu does not have a property SelectedItem.
Alternative
My alternative option would be to use ListView, because it contains a property SelectedItem. But preferably I'd like to use Menu.
Question
How can I get the selected item from the Hierarchical Data Template?
The Menu control does not have a notion of a selected item, as its MenuItems are essentially buttons that should execute an action when clicked. You can set the IsCheckable property to true to be a able to check menu items like a CheckBox, but I guess that does not fit your use-case here.
Since you probably want to display different content depending on which menu item was clicked, you could use a command, that is executed when you click a MenuItem and gets the corresponding view model as parameter and sets a property on a view model that can be bound by the ContentPresenter.
Introduce a Selected property of type PageViewModel and a Select command in your main view model that contains the Navigation porperty. The command sets the Selected property.
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
Selected = new RelayCommand<PageViewModel>(pageViewModel => Selected = pageViewModel);
// ...other code.
}
public DelegateCommand<PageViewModel> Select { get; }
private PageViewModel _selected;
public PageViewModel Selected
{
get => _selected;
private set
{
if (_selected == value)
return;
_selected = value;
OnPropertyChanged();
}
}
// ...other code.
}
You can replace the RelayCommand by the command implementation that you have at your disposal.
Then, you can adapt your style to bind the command on the main view model (either using ElementName or RelativeSource to the data context) and to bind the view model of the clicked MenuItem as command parameter. This way it is passed to the command, which sets it as Selected.
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Path=Title}" />
<Setter Property="Template" Value="{StaticResource VsMenuTop}" />
<Setter Property="Command" Value="{Binding DataContext.Select, ElementName=NavigationTreeView}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
Bind the Content of your ContentPresenter to the Selected property on the main view model.
<ContentPresenter Grid.Row="1" Content="{Binding ElementName=NavigationTreeView, Path=Selected}" />
Сan think of many implementations, but it is not entirely clear what you need.
Additional details required.
SelectedItem (property of the Selector class) contains not a UI element, but a collection source element.
Maybe you too need it , not a MenuItem?
You are not using commands, so you can use them to solve the problem.
I have implemented a simple proxy using BaseInpc and RelayCommand classes.
using Simplified;
namespace SelectedItem
{
public class SelectedItemProxy : BaseInpc
{
private object _selectedMenuItem;
public object SelectedMenuItem { get => _selectedMenuItem; set => Set(ref _selectedMenuItem, value); }
private RelayCommand _selectItemCommand;
public RelayCommand SelectItemCommand => _selectItemCommand
?? (_selectItemCommand = new RelayCommand(item => SelectedMenuItem = item));
}
}
An example of its use:
<Window x:Class="SelectedItem.SmiExamleWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SelectedItem"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="SmiExamleWindow" Height="450" Width="800">
<Window.Resources>
<local:SelectedItemProxy x:Key="proxy"/>
</Window.Resources>
<Grid>
<Menu x:Name="NavigationTreeView" IsMainMenu="True" ItemsSource="{Binding Navigation}" Grid.Row="0" VerticalAlignment="Top">
<Menu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding}"/>
<Setter Property="Command" Value="{Binding SelectItemCommand, Source={StaticResource proxy}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</Menu.Resources>
<sys:String>First</sys:String>
<sys:String>Second</sys:String>
</Menu>
<ContentPresenter Content="{Binding SelectedMenuItem, Source={StaticResource proxy}}" VerticalAlignment="Bottom" />
</Grid>
</Window>

WPF - how do I give a command to menu item with children

I have a databound hierarchical menu in WPF. All items are displayed, but the commands only fire for the leafs of the menu, not the items that have children. I'm guessing the command is overriden by expanding the child menu...
How do I get the command to execute even for the menu items with children?
What I have now is
<UserControl ...>
<WrapPanel>
<Menu>
<Menu.Resources>
<Style x:Key="MenuItemStyle" TargetType="MenuItem" d:DataContext="{d:DesignInstance local:TreeItem}">
<Setter Property="Command" Value="{Binding DataContext.AddColumnCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</Menu.Resources>
<MenuItem Header="Add ▼" ItemsSource="{Binding AvailableFields}">
<MenuItem.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:TreeItem}" ItemsSource="{Binding NestedItems}" ItemContainerStyle="{StaticResource MenuItemStyle}">
<ContentPresenter Content="{Binding Annotation}"/>
</HierarchicalDataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
</WrapPanel>
</UserControl>
I found a question with a similar name, but the situation is different and it doesn't have a good answer anyway.
All items are displayed, but the commands only fire for the leafs of the menu, not the items that have children.
Yes, this is the expected behaviour since clicking on a MenuItem with children is supposed to expand the submenu of child items. It doesn't execute a command.
If you want to expand the child items and execute the command you could handle the PreviewMouseLeftButtonDown event of the MenuItem:
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding DataContext.AddColumnCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnMouseDown" />
</Style>
-
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null && mi.Command != null && mi.HasItems)
mi.Command.Execute(mi.CommandParameter);
}
Note that handling the event in the code-behind of the view doesn't really break the MVVM pattern here since you are just invoking the command of the view model from the code-behind instead of invoking it from the XAML markup of the same view. But if you don't like this approach you could use an attached behaviour: https://www.codeproject.com/articles/28959/introduction-to-attached-behaviors-in-wpf

Two way binding of IsChecked within a nested MenuItem

I have a nested context menu like this:
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Thing Count" ItemsSource="{Binding ThingsProvider}">
NB the Thing Count parent MenuItem with children from ThingsProvider.
ThingsProvider provides a List of ThingViewModel which contains properties Thing and IsChecked. I want to be able to control IsChecked from my main view model and from the user via the MenuItem but I have hit a problem; If I use an ItemContainerStyle like this.
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Thing, Mode=OneWay}"/>
<Setter Property="IsChecked" Value="{Binding Path=IsChecked, UpdateSourceTrigger=PropertyChanged}"/>
</Style>
</MenuItem.ItemContainerStyle>
then (predictably I suppose as it's a style) it will observe the ViewModel's IsChecked but will not set it.
If I use an ItemTemplate like so
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Thing, Mode=OneWay}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
I can't get to the MenuItem's IsChecked property as I've only got the TextBlock. If I put a MenuItem in the ItemTemplate then I end up with nested MenuItems in my UI and it looks bad.
There must be a way to do this but I'm stumped at the moment, can anyone help?
Cheers
Rich

Making item selected of a ListView of ToggleButtons

I have a ListView and each item has a toggle button. I want the to be able to toggle the button when the item is selected from the list, and untoggle when the item is deselected. It has to follow mvvm, so no code behind.
Here is my setup:
<ListView x:Name="stampList"
ItemsSource="{Binding AllStampImages}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="0,2,0,0">
<ToggleButton Width="72"
Height="72"
Command="{Binding StampSelectedCommand}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My problem is, when I hit the toggle button the item is not selected. Likewise, when I hit outside the toggle button (still within the boundaries of the listView item), the item is selected but the button is not toggled.
How do I tie the two together?
You might have to bind the Selector.IsSelected property to you custome property. In this case to property of you toggle button.
Try something like:
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Selector.IsSelected" Value="{Binding [Value of your toggle button], Mode=TwoWay}" />
</Style>
</ListView.ItemContainerStyle>
The cleanest way to do that is to have a collection of items provided by ViewModel or Model layer.
Each item should have a IsSelected boolean property :
class LineItem
{
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set { isSelected = value; }
}
private String label;
public String Label
{
get { return label; }
set { label = value; }
}
}
There should be some binding to the model IsSelected property :
2.1 On the ListView.ItemTemplate
<ListView x:Name="list1" SelectionMode="Multiple" ItemContainerStyle="{DynamicResource ListViewItemStyle1}" Margin="0,0,334,0">
<ListView.ItemTemplate>
<DataTemplate>
<ToggleButton Margin="5" Content="{Binding Label}" IsChecked="{Binding IsSelected}"/>
</DataTemplate>
</ListView.ItemTemplate>
2.2 On the ListViewItem Template
Create a Template with right clicking on the listview,
In the menu : "Edit additional templates/Edit generated Item container (Empty).
Fill with following code :"
<Style x:Key="ListViewItemStyle1" TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Grid x:Name="Bd">
<ContentPresenter />
</Grid>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="#FF204080"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The trigger is based on Model IsSelected property.
The Grid is named Bd in order to change its background in the trigger.
Link to a full working demo : http://1drv.ms/1iyvPgt
Note
I trie to answer closely to the question .
But in my humble opinion, I would not use a ListView May be you have good reasons I gnore.
I 'd prefer to use an ItemsControl that is a listbox that doesn't allow to make a selection.
I'd put Toggle button, with custom Template to put a blue background if needed.
It would remove all the trigger stuff
, ...
Best coding

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>

Categories