2012-11-24 113 views
1

我曾经为我自己设计过一个非常基本的WPF练习,也就是从ViewModel动态填充菜单,我遇到了一个奇怪的问题。考虑下面的主窗口中的标记:WPF DataTemplates - 为什么渲染的区别?

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

和视图模型(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>(); 
} 

我希望GUI来准确再现两个commented-的结果在标记中出现两行 - 两个菜单项称为文件和编辑。

然而,纸版奇怪的行为鼠标悬停:

标记版本:

enter image description here

绑定版本:

enter image description here

为什么他们有什么不同?

+0

我认为这应该帮助你。同样的问题。 http://weblogs.asp.net/okloeten/archive/2007/11/14/5149692.aspx –

+0

情况类似,但不完全相同。我想在阅读这篇文章之前我必须再读几遍这篇文章。我认为'HierarchicalDataTemplate'应该通过隐式地创建一个'MenuItem'树来解决这个问题,这样就不会出现没有可视化表示的子项的问题。 –

+0

地狱谁低估了这个? –

回答

3

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

当您在菜单上设置itemssource时,它将为集合中的每个对象创建一个MenuItem,如果您还提供了带有itemssource集的HierarchicalDataTemplate,它将为该集合中的每个子对象创建MenuItems以及在层次结构中。

就你而言,你已经在模板中自己添加了一个MenuItem,这是不需要的。框架为您隐式创建这些项目。这导致菜单行为异常。

因此,要获得一个正确的结果,你应该做这样的事情:

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

更新
通过对一些设置一个DataTemplate,你告诉WPF要控制,怎么它的每一个应该显示项目。

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

将这种类型的模板应用于对象时,您将模板中的任何内容都用作标题,并且将通过将模板应用于集合集中的每个子对象来创建项目集合作为模板上的ItemsSource。所以它会递归地将模板应用到层次结构中的所有对象。

在你的例子中,你有一个菜单。你可以只通过这样创造的:

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

它的工作很好,但因为你还没有应用模板,告诉它如何集合中的项目应显示,它只是创造一个菜单项为itemssource中的每个对象,并在其上调用ToString()。这个值将被用作MenuItem的Header属性。

由于这不是你想要的,所以你必须应用一个模板,告诉WPF你想要显示的内容在隐式生成的MenuItem的头部中。

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

更新2
如果您现在想对隐式创建的菜单项设置属性,可以通过设置在HierarchicalDataTemplateItemContainerStyle财产必须是。这里定义的样式将应用于所有生成的菜单项。

所以在菜单项的命令属性绑定到该视图模型命令属性,你可以这样做:

<HierarchicalDataTemplate.ItemContainerStyle> 
    <Style TargetType="MenuItem"> 
     <Setter Property="Command" 
       Value="{Binding Command}" /> 
    </Style> 
</HierarchicalDataTemplate.ItemContainerStyle> 
+0

谢谢;就像@ kmatyaszek在下面的答案也会产生正确的视觉效果,但我仍然不确定我是否真的明白为什么。解释为什么我所做的是错误的是非常清楚,但我不明白为什么StackPanel是必要的 - 不要MenuItems知道如何显示子菜单项已经?我想要做的就是我)让他们出席并且ii)设置他们的文本。在我看来,'DataTemplate'控制数据 - 而StackPanel不是数据,它是布局。为什么涉及? –

+0

DataTemplate现在定义您想要显示的数据。我已经更新了我的答案来解释这一点。 –

+0

谢谢。不完全相信WPF在这种情况下表达了它实际上非常清楚的事情。这有点复杂的事情,因为我打算将Command绑定到每个MenuItem - 但由于它们是由模板隐式生成的,所以我不再有权访问标记中的Command属性。 –

1

试试这个HierarchicalDataTemplate

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

菜单项的ControlTemplate示例(msdn link

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

好吧,让我们看看现在的视觉树。

如果我们有这样的事情:

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

视觉的这棵树如下表示:

enter image description here

好了,MenuItem具有ContentPresenterTextBlock

如果我们有HierarchicalDataTemplate会发生什么?

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

让我们视觉上的树看到:

enter image description here

哇,它是什么???

所以,如果你没有指定的MenuItemControlTemplate做的,它本身就是MenuItemContentPresenter(你可以看到这第二个屏幕上)。因此,如果您想在HierarchicalDataTemplate(第一个屏幕)中使用它,则必须覆盖MenuItemControlTemplate

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

enter image description here

+0

谢谢,值得一试,但我需要问 - 为什么?这意味着做什么不同? –

+0

@TomW再次检查我的答案:) – kmatyaszek

+0

谢谢。我认为我现在明白了一点,并且我相信所得到的视觉树的例子。这似乎与@Peter Hansen的答案相矛盾,因为在HierarchicalDataTemplate中有一个'MenuItem'。我导致相信这会为你创建MenuItems,所以这不会产生一个MenuItem作为它的Header属性吗? –