WPF DataTemplates - 为什么渲染的差异?

时间:2012-11-24 09:24:02

标签: wpf data-binding mvvm

我遇到了一个奇怪的问题,我为自己设计了一个非常基本的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。

但是,绑定版本在鼠标悬停时表现奇怪:

标记版本:

enter image description here

绑定版本:

enter image description here

为什么他们不同?

2 个答案:

答案 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>

这个的可视树如下所示:

enter image description here

好的,MenuItem ContentPresenterTextBlockHierarchicalDataTemplate

如果我们有 <Menu.ItemTemplate> <HierarchicalDataTemplate > <MenuItem Header="{Binding Text, Mode=OneTime}" ItemsSource="{Binding MenuItems}"/> </HierarchicalDataTemplate> </Menu.ItemTemplate> 会怎样?

ControlTemplate

让我们在视觉树上看到:

enter image description here

哇,这是什么???

因此,如果您未指定MenuItem的{​​{1}},则ContentPresenter本身就是MenuItem(您可以在第二个屏幕上看到此内容)。因此,如果您想在ControlTemplate(第一个屏幕)中使用MenuItem,则必须覆盖HierarchicalDataTemplate的{​​{1}}。

下面是我的解决方案的可视树:

enter image description here