为TreeViewItem的所有子项添加边框

8

我有一个 TreeView,我想实现一种样式,它可以让我在使用 HierarchicalDataTemplate 时在特定节点的所有子节点周围放置一个边框。下面是我想要的示例:

enter image description here

以下是我目前的代码。

<HierarchicalDataTemplate DataType="{x:Type model:Node}" ItemsSource="{Binding Children, Mode=OneWay}">
     <StackPanel>
          <TextBlock Text="{Binding Name}"/>
     </StackPanel>
     <HierarchicalDataTemplate.ItemContainerStyle>
          <Style TargetType="{x:Type TreeViewItem}">
              //what goes in here???
          </Style>
     </HierarchicalDataTemplate.ItemContainerStyle>     
</HierarchicalDataTemplate>

我需要添加什么才能按照我想要的方式实现边框?
2个回答

12
为了在TreeViewItem的一组子元素周围渲染一个边框,我们需要修改TreeView的ItemContainerStyle的Style。默认情况下,TreeViewItem的样式使用来呈现其子元素的内容。在默认的ItemContainerStyle中,子元素的内容由...给出。
<ItemsPresenter x:Name="ItemsHost"
                Grid.Row="1"
                Grid.Column="1"
                Grid.ColumnSpan="2" />

现在我想测试一下,我有一个包含布尔值 Type 的集合,并尝试在这个布尔值为True时渲染一个 Border

因此,我更新了 ItemsPresenter

<Border Grid.Row="1"
        Grid.Column="1"
        Grid.ColumnSpan="2"
        BorderThickness="1">
  <Border.Style>
    <Style TargetType="{x:Type Border}">
      <Setter Property="BorderBrush"
              Value="Transparent" />
      <Style.Triggers>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor,
                                       AncestorType={x:Type TreeViewItem}},
                                       Path=DataContext.Type}"
                      Value="True">
          <Setter Property="BorderBrush"
                  Value="Red" />
        </DataTrigger>
      </Style.Triggers>
    </Style>
  </Border.Style>
  <ItemsPresenter x:Name="ItemsHost"  />
</Border>

接下来渲染如下:

在此输入图片描述

当然,您需要根据自己想要渲染Border的情况更新上述绑定。

在我的情况下,我的Type变量设置为True,因为其标题内容为"1.1"的项目。


抱歉,我发现你的回答让人有些困惑。当看到最后的代码片段时,我不知道边框在哪里。那是在你第一个代码片段中的HierarchicalDataTemplate内的边框吗?我要将你的新代码添加到ItemContainerStyle中吗?基本上我不知道要写什么和在哪里写。 - Joseph Devlin
@Kazuo 我稍后会在我的回答中添加一个示例项目,并提供下载链接。这只是一个应用于TreeView XAML元素的ItemContainerStyle。 - Viv
我仍在努力使您的解决方案运行。为了满足我的需求,所有XAML样式需要包含在HierarchicalDataTemplate内部。我不能对树视图本身进行任何更改。当我让您的解决方案工作时,它有相同的问题,即在每个子项周围放置边框。因为我只有Visual Studio 2010,所以我必须在Notepad++中阅读您的下载文件。无法打开您的项目以查看您的样式实现。 - Joseph Devlin
@Kazuo,如果你只有VS2010,你不必在NotePad++中打开东西,只需搜索“将vs2012 sln转换为vs2010”,然后你就会找到https://dev59.com/yWct5IYBdhLWcg3wXsQy,这正是我在新下载中所做的,以使其适用于VS2010。 - Viv
如果您无法更改基本样式,则需要将基本模板复制到此TreeView中,并仅在此模板中添加边框(如果您正在派生样式,则不需要复制常见依赖项,您只需要提供TreeViewItem的模板,因为其他所有内容都应通过父范围的资源找到),或者说服您必须说服的人修改基本样式并在除此TreeView之外的所有地方呈现透明边框。 - Viv
显示剩余12条评论

3

看起来WPF团队认为没有人需要这个功能,所以他们没有在TreeViewItem模板中包含任何围绕ItemsPresenter的边框,因此您需要更改TreeViewItem模板并在ItemsPresenter周围添加边框。

您可以通过下载WPF主题XAML字典来查看默认的TreeViewItem样式/模板定义。这里提供了链接here

以下是一个完整的工作解决方案的XAML:

<Window x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:model="clr-namespace:WpfApplication">

    <Window.DataContext>
        <x:ArrayExtension Type="{x:Type model:Node}">
            <model:Node Name="Root">
                <model:Node.Children>
                    <model:Node Name="Child 1" HasChildrenBorder="True">
                        <model:Node.Children>
                            <model:Node Name="Child 1.1"/>
                            <model:Node Name="Child 1.2"/>
                            <model:Node Name="Child 1.3"/>
                        </model:Node.Children>
                    </model:Node>
                    <model:Node Name="Child 2"/>
                </model:Node.Children>
            </model:Node>
        </x:ArrayExtension>
    </Window.DataContext>

    <TreeView ItemsSource="{Binding}">

        <TreeView.Resources>

            <!--This part is extracted from Areo.NormalColor.xaml WPF Theme which you can download from locations explained here: https://dev59.com/9lHTa4cB1Zd3GeqPU9FR#4158681-->
            <PathGeometry x:Key="TreeArrow">
                <PathGeometry.Figures>
                    <PathFigureCollection>
                        <PathFigure IsFilled="True"
                                    StartPoint="0 0"
                                    IsClosed="True">
                            <PathFigure.Segments>
                                <PathSegmentCollection>
                                    <LineSegment Point="0 6"/>
                                    <LineSegment Point="6 0"/>
                                </PathSegmentCollection>
                            </PathFigure.Segments>
                        </PathFigure>
                    </PathFigureCollection>
                </PathGeometry.Figures>
            </PathGeometry>

            <Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}">
                <Setter Property="Focusable" Value="False"/>
                <Setter Property="Width" Value="16"/>
                <Setter Property="Height" Value="16"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ToggleButton}">
                            <Border Width="16" 
                                    Height="16" 
                                    Background="Transparent" 
                                    Padding="5,5,5,5">
                                <Path x:Name="ExpandPath" 
                                      Fill="Transparent" 
                                      Stroke="#FF989898" 
                                      Data="{StaticResource TreeArrow}">
                                    <Path.RenderTransform>
                                        <RotateTransform 
                                            Angle="135" 
                                            CenterX="3" 
                                            CenterY="3"/>
                                    </Path.RenderTransform>
                                </Path>
                            </Border>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsMouseOver" Value="True">
                                    <Setter TargetName="ExpandPath" Property="Stroke" Value="#FF1BBBFA"/>
                                    <Setter TargetName="ExpandPath" Property="Fill" Value="Transparent"/>
                                </Trigger>
                                <Trigger Property="IsChecked" Value="True">
                                    <Setter TargetName="ExpandPath" Property="RenderTransform">
                                        <Setter.Value>
                                            <RotateTransform 
                                                Angle="180" 
                                                CenterX="3" 
                                                CenterY="3"/>
                                        </Setter.Value>
                                    </Setter>
                                    <Setter TargetName="ExpandPath" Property="Fill" Value="#FF595959"/>
                                    <Setter TargetName="ExpandPath" Property="Stroke" Value="#FF262626"/>
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>

            <Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type TreeViewItem}">
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition MinWidth="19" Width="Auto"/>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition/>
                                </Grid.RowDefinitions>
                                <ToggleButton x:Name="Expander"
                                              Style="{StaticResource ExpandCollapseToggleStyle}"
                                              IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
                                              ClickMode="Press"/>
                                <Border Name="Bd"
                                        Grid.Column="1"
                                        Background="{TemplateBinding Background}"
                                        BorderBrush="{TemplateBinding BorderBrush}"
                                        BorderThickness="{TemplateBinding BorderThickness}"
                                        Padding="{TemplateBinding Padding}"
                                        SnapsToDevicePixels="True">
                                    <ContentPresenter x:Name="PART_Header"
                                                      ContentSource="Header"
                                                      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                                </Border>
                                <Border Name="ItemsHostBd" 
                                        Grid.Row="1" 
                                        Grid.Column="1" 
                                        Grid.ColumnSpan="2">
                                    <ItemsPresenter x:Name="ItemsHost"/>
                                </Border>
                            </Grid>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsExpanded" Value="False">
                                    <Setter TargetName="ItemsHostBd" Property="Visibility" Value="Collapsed"/>
                                </Trigger>
                                <Trigger Property="HasItems" Value="False">
                                    <Setter TargetName="Expander" Property="Visibility" Value="Hidden"/>
                                </Trigger>
                                <Trigger Property="IsSelected" Value="True">
                                    <Setter TargetName="Bd" Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                                    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
                                </Trigger>
                                <MultiTrigger>
                                    <MultiTrigger.Conditions>
                                        <Condition Property="IsSelected" Value="True"/>
                                        <Condition Property="IsSelectionActive" Value="False"/>
                                    </MultiTrigger.Conditions>
                                    <Setter TargetName="Bd" Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                                    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
                                </MultiTrigger>
                                <Trigger Property="IsEnabled" Value="False">
                                    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                                </Trigger>

                                <!-- This part is customized to work with HasChildrenBorder property from data-bound object. -->
                                <DataTrigger Binding="{Binding HasChildrenBorder}" Value="True">
                                    <Setter TargetName="ItemsHostBd" Property="BorderBrush" Value="Red"/>
                                    <Setter TargetName="ItemsHostBd" Property="BorderThickness" Value="1"/>
                                </DataTrigger>

                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>

            <HierarchicalDataTemplate DataType="{x:Type model:Node}" ItemsSource="{Binding Children}">
                <TextBlock Text="{Binding Name}"/>
            </HierarchicalDataTemplate>

        </TreeView.Resources>

    </TreeView>
</Window>

这是Node类的定义方式:

using System.Collections.ObjectModel;

namespace WpfApplication
{
    public class Node
    {
        public string Name { get; set; }
        public ObservableCollection<Node> Children { get; set; }

        public bool HasChildrenBorder { get; set; }

        public Node()
        {
            this.Children = new ObservableCollection<Node>();
        }
    }
}

1
谢谢您的回答,但我不希望我的类包含任何与它们在UI中应该如何呈现相关的数据。在我看来,最好的解决方案是仅使用XAML。 - Joseph Devlin
属性HasChildrenBorder只是用作示例来展示概念的工作原理。您可以将任何其他应该为TreeViewItem子项设置边框的属性放在那里。然后,您只需要更改TreeViewItem的ControlTemplate中的DataTrigger.Binding。 - Stipo
啊,我的错误。谢谢你的回答。我在另一个帖子的帮助下找到了解决问题的方法。 - Joseph Devlin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接