1

我为自己设计的一个非常基本的 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>

和视图模型:

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 能够准确地再现标记中两个注释掉的行的结果 - 两个 MenuItems 称为 File 和 Edit。

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

标记版本:

在此处输入图像描述

绑定版本:

在此处输入图像描述

为什么它们不同?

4

2 回答 2

3

你得到了有趣的结果,因为你并没有真正使用HierarchicalDataTemplate正确的方法。

当您在 Menu 上设置 itemssource 时,它​​将为集合中的每个对象创建一个 MenuItem,如果您还提供了HierarchicalDataTemplate一个 itemssource 集,它还将为该集合中的每个子对象创建 MenuItems,在等级制度。

在您的情况下,您自己在模板中添加了一个 MenuItem,这不是必需的。框架为您隐式创建这些项目。这导致菜单行为异常。

因此,要获得正确的结果,您应该执行以下操作:

<HierarchicalDataTemplate ItemsSource="{Binding MenuItems}">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding Text}" />
    </StackPanel>
</HierarchicalDataTemplate>

更新
通过在某物上设置 DataTemplate,您是在告诉 WPF 您想要控制其每个项目的显示方式。

在这种情况下,使用 a HierarchicalDataTemplate,它是用于生成带标题的控件的模板。这种控件包含一个标题和一个项目集合。

当您将此类模板应用于对象时,您在模板中放入的任何内容都将用作标题,并且将通过将模板应用于集合集中的每个子对象作为 ItemsSource 来创建项目集合模板。因此它将递归地将模板应用于层次结构中的所有对象。

在您的示例中,您有一个菜单。您可以通过这样做来创建它:

<Menu ItemsSource="{Binding MainMenuItems}" />

它可以正常工作,但是由于您没有应用模板,因此要告诉它应该如何显示集合中的项目,它只会为 itemssource 中的每个对象创建一个 MenuItem 并调用ToString()它。然后,此值将用作 MenuItem 的 Header 属性。

由于这不是您想要的,您必须应用一个模板来告诉 WPF 您希望在隐式生成的 MenuItem 的标题中显示什么内容。

在我的示例中,我只是制作了一个包含 TextBlock 的模板,该模板绑定到 viewmodel 上的 Text 属性。

更新 2
如果您现在想在隐式创建的菜单项上设置属性,您必须通过ItemContainerStyleHierarchicalDataTemplate. 此处定义的样式将应用于所有生成的菜单项。

因此,要将 MenuItem 的 Command 属性绑定到 viewmodel 上的 Command 属性,您可以执行以下操作:

<HierarchicalDataTemplate.ItemContainerStyle>
    <Style TargetType="MenuItem">
        <Setter Property="Command"
                Value="{Binding Command}" />
    </Style>
</HierarchicalDataTemplate.ItemContainerStyle>
于 2012-11-24T10:12:41.067 回答
1

试试这个HierarchicalDataTemplate

<HierarchicalDataTemplate>
    <MenuItem ItemsSource="{Binding MenuItems}">
        <MenuItem.Template>
             <ControlTemplate>
                    <TextBlock Text="{Binding Text, Mode=OneTime}" />
             </ControlTemplate>
         </MenuItem.Template>
     </MenuItem>
</HierarchicalDataTemplate>

MenuItem ControlTemplate 示例(msdn 链接

Windows Presentation Foundation (WPF) 中的控件有一个 ControlTemplate,其中包含该控件的可视化树。您可以通过修改控件的 ControlTemplate 来更改控件的结构和外观。没有办法只替换控件的可视化树的一部分;要更改控件的可视化树,您必须将控件的 Template 属性设置为其新的完整 ControlTemplate。

好的,现在让我们看看可视化树。

如果我们有这样的事情:

<Menu Height="26" Grid.Row="1">
     <MenuItem Header="File" />
     <MenuItem Header="Edit" />
 </Menu>

其可视化树如下所示:

在此处输入图像描述

好的,MenuItem与.ContentPresenterTextBlock

如果我们有会发生什么HierarchicalDataTemplate

 <Menu.ItemTemplate>                
            <HierarchicalDataTemplate >
                <MenuItem Header="{Binding Text, Mode=OneTime}" ItemsSource="{Binding MenuItems}"/>
            </HierarchicalDataTemplate>
  </Menu.ItemTemplate>

让我们看看可视化树:

在此处输入图像描述

哇,这是什么???

因此,如果您不指定ControlTemplateof MenuItem,它本身就是ContentPresenterof MenuItem(您可以在第二个屏幕上看到)。ControlTemplate所以,MenuItem如果你想在HierarchicalDataTemplate(第一个屏幕)中使用它,你必须覆盖它。

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

在此处输入图像描述

于 2012-11-24T09:45:30.450 回答