WPF 中自定义 TreeView 布局






4.90/5 (62投票s)
展示如何将 TreeView 变成组织结构图。
引言
本文讨论如何自定义 WPF TreeView
中的项目布局。 我们将研究的布局与“组织结构图”非常相似,其中每个级别的项目都显示在其各自父级的正下方的一行中。 在此过程中,我们将看到 WPF 中模板和样式的强大功能如何为自定义应用程序的用户界面提供令人难以置信的灵活性。
本文不适用于 WPF 初学者。 它假定您已经了解 XAML、控件模板、样式、触发器、分层数据模板、数据绑定以及 WPF 的其他基础知识。
我还在关于 TreeView
控件的布局自定义方面发布了另一篇文章。 如果您有兴趣了解自定义 TreeView
的另一种方法,您可能需要阅读 WPF 中高级自定义 TreeView 布局。
图形概览
在深入研究使魔法发生的 XAML 之前,让我们首先看一下我们想要实现的目标。 如果我用一些简单的数据填充 TreeView
并查看它,默认情况下它看起来很普通。 这是“之前”的图片
您在上面看到的肯定不是数据的令人印象深刻的表示。 但是,在我们自定义 TreeViewItem
的呈现方式以及 TreeView
如何定位其项目后,相同的 TreeView
控件可以像这样显示
工作原理
第一步是为 TreeViewItem
类创建一个自定义 ControlTemplate
。 如果您将该模板包装在一个类型化的 Style
(即没有 Key
的 Style
),则默认情况下它将自动应用于每个 TreeViewItem
实例。 TreeViewItem
控件模板应该有两个东西:一个 ContentPresenter
,其 Name
为“PART_Header”和一个 ItemsPresenter
。 ContentPresenter
用于显示项目的内容。 ItemsPresenter
用于显示它的子项目。
除了自定义 TreeViewItem
控件模板外,您还必须修改 TreeViewItem
的 ItemsPanel
。 为了使子项目显示在水平行中,我将 TreeViewItem.ItemsPanel
属性设置为一个 StackPanel
,方向为水平。 该设置也应用于之前提到的类型化的 Style
中。
让我们看一下类型化 Style
的一个节选版本
<Style TargetType="TreeViewItem">
<Style.Resources>
<!-- Resources omitted for clarity… -->
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeViewItem">
<Grid Margin="2">
<Grid.RowDefinitions>
<!--The top row contains the item's content.-->
<RowDefinition Height="Auto" />
<!--The bottom row contains the item's children.-->
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- This Border and ContentPresenter displays the
content of the TreeViewItem. -->
<Border Name="Bd"
Background="{StaticResource ItemAreaBrush}"
BorderBrush="{StaticResource ItemBorderBrush}"
BorderThickness="0.6"
CornerRadius="8"
Padding="6"
>
<ContentPresenter Name="PART_Header"
ContentSource="Header"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<!-- The ItemsPresenter displays the item's children. -->
<ItemsPresenter Grid.Row="1"/>
</Grid>
<ControlTemplate.Triggers>
<!--When the item is selected in the TreeView, use the
"selected" colors and give it a drop shadow. -->
<Trigger Property="IsSelected" Value="True">
<Setter
TargetName="Bd"
Property="Panel.Background"
Value="{StaticResource SelectedItemAreaBrush}" />
<Setter
TargetName="Bd"
Property="Border.BorderBrush"
Value="{StaticResource SelectedItemBorderBrush}" />
<Setter
TargetName="Bd"
Property="TextElement.Foreground"
Value="{DynamicResource
{x:Static SystemColors.HighlightTextBrushKey}}" />
<Setter
TargetName="Bd"
Property="Border.BitmapEffect"
Value="{StaticResource DropShadowEffect}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<!-- Make each TreeViewItem show it's children
in a horizontal StackPanel. -->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel
HorizontalAlignment="Center"
IsItemsHost="True"
Margin="4,6"
Orientation="Horizontal" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
最后一步是使 TreeView
水平居中根项目。 这样做将在项目之间提供对称性,如上图所示。 这一步只是将 TreeView
的 ItemsPanel
属性设置为一个 Grid
,其 HorizontalAlignment
设置为“Center
”。 让我们看一下包含我们自定义 TreeView
的 Window 的 XAML
<Window x:Class="CustomTreeViewLayout.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomTreeViewLayout"
Title="Custom TreeView" Height="350" Width="780"
Loaded="OnLoaded"
WindowStartupLocation="CenterScreen"
FontSize="11"
>
<TreeView Name="tree">
<TreeView.Resources>
<ResourceDictionary>
<!-- Import the resource dictionary file which
contains the Style that makes TreeViewItems
display their child items in an organization
chart layout. -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="OrgChartTreeViewItemStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- This template explains how to render
a Node object and its child nodes. -->
<HierarchicalDataTemplate
DataType="{x:Type local:Node}"
ItemsSource="{Binding ChildNodes}"
>
<TextBlock Text="{Binding Text}" />
</HierarchicalDataTemplate>
</ResourceDictionary>
</TreeView.Resources>
<!-- Put the root item(s) in a centered Grid so that
they will be centered and retain their width. -->
<TreeView.ItemsPanel>
<ItemsPanelTemplate>
<Grid
HorizontalAlignment="Center"
IsItemsHost="True" />
</ItemsPanelTemplate>
</TreeView.ItemsPanel>
</TreeView>
</Window>
我不会讨论用虚拟数据填充 TreeView
的代码。 请随意浏览该代码(以及所有其他代码),该代码可在本文顶部的源代码下载中找到。
提示
一旦我发现了一个小技巧,自定义 TreeViewItem
类的 ControlTemplate
就很容易了。 我将默认的 TreeViewItem
控件模板序列化为 XAML,然后对其进行修改,直到我得到我想要的结果。
大麻烦
不幸的是,没有受支持的方法以编程方式设置 TreeView
中的选定项目。 TreeView
的 SelectedItem
属性没有 setter。 因此,我无法自定义 TreeView
的键盘导航。 演示项目阻止 TreeView
完全响应键盘输入。 如果您在演示中启用键盘导航,您会发现导航项目非常不直观。 希望有一天能够自定义 TreeView
的键盘导航,但在此之前...