C# 如何在TreeView中仅设置顶级项目的样式?

C# 如何在TreeView中仅设置顶级项目的样式?,c#,wpf,xaml,mvvm,hierarchicaldatatemplate,C#,Wpf,Xaml,Mvvm,Hierarchicaldatatemplate,第二天我在网上搜索,还没有找到解决办法。 以这样的元素为例: <TreeView ItemsSource="{Binding Types}" Width="300"> <TreeView.Resources> <HierarchicalDataTemplate DataType="{x:Type models:Type}" I

第二天我在网上搜索,还没有找到解决办法。 以这样的元素为例:

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

我将材质NuGet库用于基础样式。然而,现在我需要在第一级项目上禁用悬停、选择等,只允许对子项目进行选择/悬停

但我发现的一切似乎都是关于设计每一项内容的样式,或是设计全球范围内的所有内容的样式

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

A听起来好像你并不希望每个顶级项目都像一个普通的
TreeViewItem
。在这种情况下,为什么不将顶级项移到
树视图
之外呢

基本上,您将拥有顶级项目的
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>

单击其中一个顶级项目以展开其下方的树


听起来好像你并不真的希望每个顶级项目都像一个普通的
TreeViewItem
。在这种情况下,为什么不将顶级项移到
树视图
之外呢

基本上,您将拥有顶级项目的
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>

单击其中一个顶级项目以展开其下方的树


您可以保留
树状视图
并设置属性或将样式应用于顶级项目(假设您的
树状视图
未嵌套在另一个
树状视图
),方法是在逻辑层次结构中搜索
树状视图

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


对于顶级项,此绑定会向调试输出发送警告,但可以安全地忽略这些警告。这个技巧的一个更复杂的版本是创建一个可继承的附加属性
TreeViewItemLevel
,该属性将在
TreeView
上设置为零,并比
TreeViewItem
s上的继承值多设置一个。您可以保留
TreeView
并设置属性或将样式应用于通过在逻辑层次结构上搜索
树视图项
,可以找到顶级项(假设您的
树视图
未嵌套在另一个
树视图
):

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


对于顶级项,此绑定会向调试输出发送警告,但可以安全地忽略这些警告。这个技巧的一个更复杂的版本是创建一个可继承的附加属性
TreeViewItemLevel
,该属性将在
TreeView
上设置为零,并比
TreeViewItem
s上的继承值多设置一个。

同时使用相对源绑定设置
DataTrigger
,它将产生绑定错误

<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="{x:Null}">
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}, FallbackValue={x:Null}}" Value="{x:Null}">
另一种既不会产生错误也不会产生警告的方法是创建一个值转换器,它基本上执行与
相对资源
绑定相同的操作,但也会处理错误。如果传入的任何依赖项对象的祖先类型为
TreeViewItem
,则返回
true
,否则返回
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">


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

当使用相对源绑定设置
DataTrigger
时,它将产生绑定错误

<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="{x:Null}">
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}, FallbackValue={x:Null}}" Value="{x:Null}">
另一种既不会产生错误也不会产生警告的方法是创建一个值转换器,它基本上执行与
相对资源
绑定相同的操作,但也会处理错误。如果传入的任何依赖项对象的祖先类型为
TreeViewItem
,则返回
true
,否则返回
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">

这种方法适用于静态分配的
treeviewitem
ItemsSource
bind