您如何只设置 TreeView 中顶级项目的样式?

How do you style only the top level items in TreeView?

第 2 天,我在网上搜索,但没有找到解决方案。 采取这样的元素:

<TreeView ItemsSource="{Binding Types}" Width="300">
   <TreeView.Resources>
      <HierarchicalDataTemplate DataType="{x:Type models:Type}"
                                ItemsSource="{Binding SubTypes}">
         <TextBlock Text="{Binding Name}"/>
         <HierarchicalDataTemplate.ItemTemplate>
            <DataTemplate DataType="{x:Type SubType}">
               <TextBlock Text="{Binding Name}"/>
            </DataTemplate>
         </HierarchicalDataTemplate.ItemTemplate>
      </HierarchicalDataTemplate>
   </TreeView.Resources>
</TreeView>

我将 Material NuGet 库用于基本样式。但是,现在我需要在第一级项目上禁用悬停、select 等,并且只允许子项目使用 selection/hover。

但我似乎发现的所有内容都是关于为每个项目的内容设置样式或全局设置所有内容的样式。

A <- remove selection/hover (pref single click too but that's another topic)
  A1 <- maintain original style, hover and select
  A2 <- maintain original style, hover and select
  A3 <- maintain original style, hover and select
B <- remove selection/hover (pref single click too but that's another topic)
  B1 <- maintain original style, hover and select
  B2 <- maintain original style, hover and select
  B3 <- maintain original style, hover and select

听起来您并不真的希望每个顶级项目都像普通项目一样运行 TreeViewItem。在那种情况下,为什么不将顶级项目 移到 TreeView 之外?

基本上,您会有 ItemsControl 个顶级项目,其中项目模板的作用有点像 Expander,其中包含 TreeView 个位于其下方的项目.您可以根据自己的喜好设置顶级项目的样式。

缺点是顶级项目下的树将被单独虚拟化,而不是作为一个整体。也就是说,他们不会共享容器。除非你有 的顶级项目,否则这可能不是什么大问题。

示例:

<ItemsControl xmlns:s="clr-namespace:System;assembly=mscorlib"
              ItemsSource="{Binding Types}">
  <ItemsControl.Resources>
    <ControlTemplate x:Key="ExpanderButtonTemplate" TargetType="ToggleButton">
      <Border Background="Transparent" Padding="3,2">
        <ContentPresenter />
      </Border>
    </ControlTemplate>
    <Style TargetType="Expander">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="Expander">
            <DockPanel LastChildFill="True">
              <ToggleButton DockPanel.Dock="Top"
                            IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
                            Template="{StaticResource ExpanderButtonTemplate}">
                <ContentPresenter ContentSource="Header" />
              </ToggleButton>
              <Border>
                <ContentPresenter x:Name="contentSite" Visibility="Collapsed" />
              </Border>
            </DockPanel>
            <ControlTemplate.Triggers>
              <Trigger Property="IsExpanded" Value="True">
                <Setter TargetName="contentSite" Property="Visibility" Value="Visible" />
              </Trigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </ItemsControl.Resources>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Expander Header="{Binding Name}">
        <TreeView ItemsSource="{Binding SubTypes}" BorderThickness="0" Padding="0">
          <TreeView.ItemTemplate>
            <HierarchicalDataTemplate DataType="{x:Type models:Type}"
                                      ItemsSource="{Binding SubTypes}">
              <TextBlock Text="{Binding Name}"/>
              <HierarchicalDataTemplate.ItemTemplate>
                <DataTemplate DataType="{x:Type models:SubType}">
                  <TextBlock Text="{Binding Name}"/>
                </DataTemplate>
              </HierarchicalDataTemplate.ItemTemplate>
            </HierarchicalDataTemplate>
          </TreeView.ItemTemplate>
        </TreeView>
      </Expander>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

单击顶级项目之一以展开其下方的树。

您可以通过向上搜索保留 TreeView 并设置属性或将样式应用到顶级项目(假设您的 TreeView 未嵌套在另一个 TreeView 中) TreeViewItem:

的逻辑层级
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="{x:Null}">
  <Setter Property="Background" Value="LightYellow" />
</DataTrigger>

对于顶级项目,此绑定会向调试输出发送垃圾邮件警告,但可以安全地忽略它们。这个技巧的一个更复杂的版本是创建一个可继承的附加 属性 TreeViewItemLevel,它将在 TreeView 上设置为零,并且比 [=14= 上的继承值多 1 ]s.

虽然使用相对源绑定设置 DataTrigger 有效,但它会产生绑定 错误

<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="{x:Null}">

通过设置 x:NullFallbackValue,您不会收到任何绑定错误,但会收到 警告.

<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}, FallbackValue={x:Null}}" Value="{x:Null}">

另一种既不会产生错误也不会产生警告的方法是创建一个值转换器,它本质上与 RelativeSource 绑定相同,但也会处理错误。它 returns true 对于传入的任何依赖对象,如果它具有 TreeViewItem 类型的祖先,否则 false

public class IsRootTreeViewItemConverter : IValueConverter
{
   public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
   {
      return value is null ? Binding.DoNothing : !HasTreeViewItemAncestor((DependencyObject)value);
   }

   public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
   {
      throw new InvalidOperationException();
   }

   private static bool HasTreeViewItemAncestor(DependencyObject child)
   {
      var parent = VisualTreeHelper.GetParent(child);

      return parent switch
      {
         null => false,
         TreeViewItem _ => true,
         _ => HasTreeViewItemAncestor(parent)
      };
   }
}

在您的 TreeViewItem 样式触发器中,您可以通过使用 Self 绑定到项目本身来使用转换器。

<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource IsRootTreeViewItemConverter}}" Value="True">

此方法适用于静态分配的 TreeViewItemsItemsSource 绑定。