我遇到了一个奇怪的问题,我为自己设计了一个非常基本的WPF练习,即从ViewModel动态填充菜单。给出以下主窗口标记:
<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>
和ViewModel:
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>();
}
我希望GUI能够完全重现标记中两条注释掉的行的结果 - 两个叫做文件和编辑的MenuItem。
但是,绑定版本在鼠标悬停时表现奇怪:
标记版本:
绑定版本:
为什么他们不同?
答案 0 :(得分:3)
你得到了有趣的结果,因为你并没有真正使用HierarchicalDataTemplate
正确的方式。
当您在菜单上设置itemssource时,它将为集合中的每个对象创建一个MenuItem,如果您还为HierarchicalDataTemplate
提供了一个itemssource集,它将为每个子对象创建MenuItems在该集合中,在层次结构中。
在您的情况下,您在模板中自己添加了一个MenuItem,这是不需要的。框架为您隐式创建这些项。这导致菜单表现得很奇怪。
为了获得正确的结果,你应该做这样的事情:
<HierarchicalDataTemplate ItemsSource="{Binding MenuItems}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Text}" />
</StackPanel>
</HierarchicalDataTemplate>
<强>更新强>
通过在某些东西上设置DataTemplate,您告诉WPF您要控制它,以及如何显示它的每个项目。
在这种情况下,使用HierarchicalDataTemplate
,这是用于生成带有标题的控件的模板。这种控件包含标题和项集合。
将此类模板应用于对象时,无论您放入模板中的任何内容都将用作标题,并且将通过将模板应用于集合集中的每个子对象来创建项集合。模板上的ItemsSource。因此,它会递归地将模板应用于层次结构中的所有对象。
在您的示例中,您有一个菜单。你可以通过这样做来创建它:
<Menu ItemsSource="{Binding MainMenuItems}" />
它可以正常工作,但由于你没有应用模板,告诉它应该如何显示集合中的项目,它只会为itemssource中的每个对象创建一个MenuItem并调用ToString()
on它。然后,此值将用作MenuItem上的Header属性。
由于这不是您想要的,您必须应用模板,告诉WPF您希望显示为隐式生成的MenuItem标题中的内容。
在我的示例中,我只创建了一个包含TextBlock的模板,该模板绑定到viewmodel上的Text属性。
更新2
如果您现在要在隐式创建的菜单项上设置属性,则必须在ItemContainerStyle
上设置HierarchicalDataTemplate
属性。此处定义的样式将应用于所有生成的菜单项。
因此,要将MenuItem的Command属性绑定到viewmodel上的Command属性,您可以执行以下操作:
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding Command}" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
答案 1 :(得分:1)
试试这个HierarchicalDataTemplate
:
<HierarchicalDataTemplate>
<MenuItem ItemsSource="{Binding MenuItems}">
<MenuItem.Template>
<ControlTemplate>
<TextBlock Text="{Binding Text, Mode=OneTime}" />
</ControlTemplate>
</MenuItem.Template>
</MenuItem>
</HierarchicalDataTemplate>
MenuItem ControlTemplate示例(msdn link)
Windows Presentation Foundation(WPF)中的控件有一个 ControlTemplate包含该控件的可视树。您可以 通过修改控件来改变控件的结构和外观 该控件的ControlTemplate。没有办法只替换部分 对照的可视树;改变一个视觉树 控件必须将控件的Template属性设置为新的 并完成ControlTemplate。
好的,让我们现在看一下可视树。
如果我们有这样的事情:
<Menu Height="26" Grid.Row="1">
<MenuItem Header="File" />
<MenuItem Header="Edit" />
</Menu>
这个的可视树如下所示:
好的,MenuItem
ContentPresenter
与TextBlock
有HierarchicalDataTemplate
。
如果我们有 <Menu.ItemTemplate>
<HierarchicalDataTemplate >
<MenuItem Header="{Binding Text, Mode=OneTime}" ItemsSource="{Binding MenuItems}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
会怎样?
ControlTemplate
让我们在视觉树上看到:
哇,这是什么???
因此,如果您未指定MenuItem
的{{1}},则ContentPresenter
本身就是MenuItem
(您可以在第二个屏幕上看到此内容)。因此,如果您想在ControlTemplate
(第一个屏幕)中使用MenuItem
,则必须覆盖HierarchicalDataTemplate
的{{1}}。
下面是我的解决方案的可视树: