Reputation: 5412
I've encountered an oddity with a very basic WPF exercise I've devised for myself, namely dynamically populating menus from a ViewModel. Given the following main window markup:
<Window x:Class="Demosne.Client.WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:project="clr-namespace:Demosne.Client.WPF">
<Grid>
<Menu Height="26" Name="menu1" VerticalAlignment="Top" HorizontalAlignment="Stretch" ItemsSource="{Binding MainMenuItems}">
<Menu.ItemTemplate>
<HierarchicalDataTemplate >
<MenuItem Header="{Binding Text, Mode=OneTime}" ItemsSource="{Binding MenuItems}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
<!--<MenuItem Header="File" />
<MenuItem Header="Edit" />-->
</Menu>
</Grid>
and the ViewModel(s):
public class MainWindowViewModel
{
private IList<MenuItemViewModel> _menuItems = new List<MenuItemViewModel>()
{
new MenuItemViewModel() { Text = "File" },
new MenuItemViewModel() { Text = "Edit" }
};
public IList<MenuItemViewModel> MainMenuItems
{
get
{
return _menuItems;
}
}
}
public class MenuItemViewModel
{
public string Text { get; set; }
public IList<MenuItemViewModel> MenuItems
{
get
{
return _menuItems;
}
}
private IList<MenuItemViewModel> _menuItems = new List<MenuItemViewModel>();
}
I would expect the GUI to exactly reproduce the the result of the two commented-out lines in the markup - two MenuItems called File and Edit.
However, the bound version behaves strangely on mouseover:
Markup version:
Bound version:
Why are they different?
Upvotes: 1
Views: 267
Reputation: 8907
You are getting funny results, because you are not really using the HierarchicalDataTemplate
the correct way.
When you set a itemssource on a Menu, it will create a MenuItem for each object in the collection, and if you also supply a HierarchicalDataTemplate
with a itemssource set, it will create MenuItems for each of the child objects in that collection as well, down the hierarchy.
In your case, you've added a MenuItem yourself in the template, which is not needed. The framework creates those items implicitly for you. And this is causing the menu to behave oddly.
So to get a correct result you should do something like this:
<HierarchicalDataTemplate ItemsSource="{Binding MenuItems}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Text}" />
</StackPanel>
</HierarchicalDataTemplate>
Update
By setting a DataTemplate on something, you are telling WPF that you want to control, how each of its items should be displayed.
In this case a HierarchicalDataTemplate
is used, which is a template for generating headered controls. This kind of control contains a header and an items collection.
When you apply this kind of template to an object, whatever you have put in the template will be used as the header, and the items collection will be created by applying the template to each of the child objects in the collection set as the ItemsSource on the template. So it will recursively apply the template to all objects in the hierarchy.
In your example, you have a Menu. You could just create it by doing this:
<Menu ItemsSource="{Binding MainMenuItems}" />
It would work fine, but since you have not applied a template, to tell it how the items in the collection should be displayed, it will just create a MenuItem for each object in the itemssource and call ToString()
on it. This value will then be used as the Header property on the MenuItem.
Since that not what you want, you have to apply a template, to tell WPF what you would like to be displayed as the content in the header of the implicitly generated MenuItem.
In my example I simply made a template containing a TextBlock, which binds to the Text property on the viewmodel.
Update 2
If you now want to set properties on the implicitly created menuitems, you have to that by setting the ItemContainerStyle
property on the HierarchicalDataTemplate
. The styled defined here will be applied to all the generated menuitems.
So to bind the Command property of the MenuItem to a Command property on the viewmodel you can do this:
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding Command}" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
Upvotes: 3
Reputation: 19296
Try this HierarchicalDataTemplate
:
<HierarchicalDataTemplate>
<MenuItem ItemsSource="{Binding MenuItems}">
<MenuItem.Template>
<ControlTemplate>
<TextBlock Text="{Binding Text, Mode=OneTime}" />
</ControlTemplate>
</MenuItem.Template>
</MenuItem>
</HierarchicalDataTemplate>
MenuItem ControlTemplate Example (msdn link)
Controls in Windows Presentation Foundation (WPF) have a ControlTemplate that contains the visual tree of that control. You can change the structure and appearance of a control by modifying the ControlTemplate of that control. There is no way to replace only part of the visual tree of a control; to change the visual tree of a control you must set the Template property of the control to its new and complete ControlTemplate.
OK let's see now on the visual tree.
If we have something like this:
<Menu Height="26" Grid.Row="1">
<MenuItem Header="File" />
<MenuItem Header="Edit" />
</Menu>
Visual tree of this is represented below:
Ok, so MenuItem
has ContentPresenter
with TextBlock
.
What happens if we have HierarchicalDataTemplate
?
<Menu.ItemTemplate>
<HierarchicalDataTemplate >
<MenuItem Header="{Binding Text, Mode=OneTime}" ItemsSource="{Binding MenuItems}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
Let's see on the visual tree:
Wow, what it is???
So if you don't specified ControlTemplate
of MenuItem
, it is itself ContentPresenter
of the MenuItem
(you can see this on the second screen). So, you must override ControlTemplate
of the MenuItem
if you want use it in HierarchicalDataTemplate
(first screen).
Below is the visual tree with my solution:
Upvotes: 1