前言
本文将探讨如何利用WPF框架实现树形表格控件,该控件不仅能够有效地展示复杂的层级数据,还能够提供丰富的个性化定制选项。我们将介绍如何使用WPF提供的控件、模板、布局、数据绑定等技术来构建这样一个树形表格。
一、运行效果
1.1默认样式
1.2 自定义样式
二、代码实现
2.1 创建自定义控件(TreeListView)
新建一个继承自TreeView的控件,并定义一个类型为ViewBase的View依赖属性,用于在代码中指定列。
public class TreeListView : TreeView { public ViewBase View { get { return (ViewBase)GetValue(ViewProperty); } set { SetValue(ViewProperty, value); } } public static readonly DependencyProperty ViewProperty = DependencyProperty.Register("View", typeof(ViewBase), typeof(TreeListView)); }
2.2在TreeListView控件模板中处理列头
为了在TreeListView中显示列头,需要在合适的位置添加GridViewHeaderRowPresenter控件,并在Columns属性上绑定我们之前定义的View.Columns属性。下面我们首先来分析TreeView控件模板的代码。
ControlTemplate TargetType="{x:Type TreeView}">
Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="true">
ScrollViewer x:Name="_tv_scrollviewer_" Background="{TemplateBinding Background}" CanContentScroll="false" Focusable="false" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
ItemsPresenter/>
ScrollViewer>
Border>
ControlTemplate.Triggers>
Trigger Property="IsEnabled" Value="false">
Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
Trigger>
Trigger Property="VirtualizingPanel.IsVirtualizing" Value="true">
Setter Property="CanContentScroll" TargetName="_tv_scrollviewer_" Value="true"/>
Trigger>
ControlTemplate.Triggers>
ControlTemplate>
通过以上代码我们可以看出,只要将GridViewHeaderRowPresenter控件添加到ScrollViewer控件上面即可实现列头功能,但这样会有一个问题,那就是内容宽度超出控件宽度后,鼠标拖动横向滚动条时列头不会跟随下方的数据列表一起滚动。为解决这个问题我们需要将GridViewHeaderRowPresenter放置到ScrollViewer控件模板中,以下为完整代码。
Style x:Key="{x:Static GridView.GridViewScrollViewerStyleKey}" TargetType="{x:Type ScrollViewer}">
Setter Property="Focusable" Value="false" />
Setter Property="Template">
Setter.Value>
ControlTemplate TargetType="{x:Type ScrollViewer}">
Grid Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
Grid.ColumnDefinitions>
ColumnDefinition Width="*" />
ColumnDefinition Width="Auto" />
Grid.ColumnDefinitions>
Grid.RowDefinitions>
RowDefinition Height="*" />
RowDefinition Height="Auto" />
Grid.RowDefinitions>
DockPanel Margin="{TemplateBinding Padding}">
ScrollViewer
DockPanel.Dock="Top"
Focusable="false"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden">
GridViewHeaderRowPresenter
Margin="2,0,2,0"
AllowsColumnReorder="{Binding TemplatedParent.View.AllowsColumnReorder, RelativeSource={RelativeSource TemplatedParent}}"
ColumnHeaderContainerStyle="{Binding TemplatedParent.View.ColumnHeaderContainerStyle, RelativeSource={RelativeSource TemplatedParent}}"
ColumnHeaderContextMenu="{Binding TemplatedParent.View.ColumnHeaderContextMenu, RelativeSource={RelativeSource TemplatedParent}}"
ColumnHeaderStringFormat="{Binding TemplatedParent.View.ColumnHeaderStringFormat, RelativeSource={RelativeSource TemplatedParent}}"
ColumnHeaderTemplate="{Binding TemplatedParent.View.ColumnHeaderTemplate, RelativeSource={RelativeSource TemplatedParent}}"
ColumnHeaderTemplateSelector="{Binding TemplatedParent.View.ColumnHeaderTemplateSelector, RelativeSource={RelativeSource TemplatedParent}}"
ColumnHeaderToolTip="{Binding TemplatedParent.View.ColumnHeaderToolTip, RelativeSource={RelativeSource TemplatedParent}}"
Columns="{Binding TemplatedParent.View.Columns, RelativeSource={RelativeSource TemplatedParent}}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
ScrollViewer>
ScrollContentPresenter
x:Name="PART_ScrollContentPresenter"
CanContentScroll="{TemplateBinding CanContentScroll}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
KeyboardNavigation.DirectionalNavigation="Local"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
DockPanel>
ScrollBar
x:Name="PART_HorizontalScrollBar"
Grid.Row="1"
Cursor="Arrow"
Maximum="{TemplateBinding ScrollableWidth}"
Minimum="0.0"
Orientation="Horizontal"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
ScrollBar
x:Name="PART_VerticalScrollBar"
Grid.Column="1"
Cursor="Arrow"
Maximum="{TemplateBinding ScrollableHeight}"
Minimum="0.0"
Orientation="Vertical"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
DockPanel
Grid.Row="1"
Grid.Column="1"
Background="{Binding Background, ElementName=PART_VerticalScrollBar}"
LastChildFill="false">
Rectangle
Width="1"
DockPanel.Dock="Left"
Fill="White"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" />
Rectangle
Height="1"
DockPanel.Dock="Top"
Fill="White"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" />
DockPanel>
Grid>
ControlTemplate>
Setter.Value>
Setter>
Style>
Style TargetType="{x:Type local:TreeListView}">
Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
Setter Property="ScrollViewer.CanContentScroll" Value="true" />
Setter Property="Template">
Setter.Value>
ControlTemplate TargetType="{x:Type local:TreeListView}">
Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
ScrollViewer Padding="{TemplateBinding Padding}" Style="{StaticResource {x:Static GridView.GridViewScrollViewerStyleKey}}">
ItemsPresenter />
ScrollViewer>
Border>
ControlTemplate>
Setter.Value>
Setter>
Style>
2.3 在TreeListViewItem模板中处理子项的展开和收缩
新建一个继承自TreeViewItem的类,命名为TreeListViewItem(如有个性化需求,可以在该类中处理),编辑控件模板,在模板中添加以下代码。
Style TargetType="{x:Type local:TreeListViewItem}">
Setter Property="BorderThickness" Value="1" />
Setter Property="Template">
Setter.Value>
Co服务器托管ntrolTemplate TargetType="{x:Type local:TreeListViewItem}">
StackPanel>
Border
Name="Bd"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
GridViewRowPresenter
x:Name="PART_Header"
服务器托管 Columns="{Binding RelativeSource={RelativeSource AncestorType=local:TreeListView}, Path=View.Columns}"
Content="{TemplateBinding Header}" />
Border>
ItemsPresenter x:Name="ItemsHost" />
StackPanel>
ControlTemplate.Triggers>
Trigger Property="IsExpanded" Value="false">
Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed" />
Trigger>
MultiTrigger>
MultiTrigger.Conditions>
Condition Property="HasHeader" Value="false" />
Condition Property="Width" Value="Auto" />
MultiTrigger.Conditions>
Setter TargetName="PART_Header" Property="MinWidth" Value="75" />
MultiTrigger>
MultiTrigger>
MultiTrigger.Conditions>
Condition Property="HasHeader" Value="false" />
Condition Property="Height" Value="Auto" />
MultiTrigger.Conditions>
Setter TargetName="PART_Header" Property="MinHeight" Value="19" />
MultiTrigger>
MultiTrigger>
MultiTrigger.Conditions>
Condition Property="extensions:TreeViewItemExtensions.IsMouseDirectlyOverItem" Value="True" />
MultiTrigger.Conditions>
Setter TargetName="Bd" Property="Background" Value="{StaticResource Item.MouseOver.Background}" />
Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource Item.MouseOver.Border}" />
MultiTrigger>
MultiTrigger>
MultiTrigger.Conditions>
Condition Property="Selector.IsSelectionActive" Value="False" />
Condition Property="IsSelected" Value="True" />
MultiTrigger.Conditions>
Setter TargetName="Bd" Property="Background" Value="{StaticResource Item.SelectedInactive.Background}" />
Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource Item.SelectedInactive.Border}" />
MultiTrigger>
MultiTrigger>
MultiTrigger.Conditions>
Condition Property="Selector.IsSelectionActive" Value="True" />
Condition Property="IsSelected" Value="True" />
MultiTrigger.Conditions>
Setter TargetName="Bd" Property="Background" Value="{StaticResource Item.SelectedActive.Background}" />
Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource Item.SelectedActive.Border}" />
MultiTrigger>
Trigger Property="IsEnabled" Value="False">
Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
Trigger>
ControlTemplate.Triggers>
ControlTemplate>
Setter.Value>
Setter>
Style>
此处的核心在于模板中添加了GridViewRowPresenter控件,并在Columns属性上绑定了我们之前定义的View.Columns属性,这样就可以在每一行上面显示列数据。还有一个关键点是ItemsPresenter,它用于显示子项数据,此处命名为ItemsHost,它由属性触发器中的代码来控件展开和收起。以下是属性触发器代码。
Trigger Property="IsExpanded" Value="false">
Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed" />
Trigger>
2.4 在单元格模板中控件子项的展开与收起
为了达到展开和收起的效果,需要在首列的单元格中控制TreeListViewItem的IsExpanded属性。以下为完整代码。
DataTemplate x:Key="ExpandCellTemplate">
DockPanel>
ToggleButton
x:Name="Expander"
Margin="{Binding Path=Level, Converter={StaticResource LevelIndentConverter}, RelativeSource={RelativeSource AncestorType={x:Type TreeListViewItem}}}"
ClickMode="Press"
IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource AncestorType={x:Type TreeListViewItem}}}"
Style="{StaticResource ExpandCollapseToggleStyle}" />
TextBlock Text="{Binding Property1}" />
DockPanel>
DataTemplate.Triggers>
DataTrigger Binding="{Binding Path=HasItems, RelativeSource={RelativeSource AncestorType={x:Type TreeListViewItem}}}" Value="False">
Setter TargetName="Expander" Property="Visibility" Value="Hidden" />
DataTrigger>
DataTemplate.Triggers>
DataTemplate>
其关键代码为
IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource AncestorType={x:Type TreeListViewItem}}}"
2.5 控件使用
TreeListView ItemsSource="{Binding Collection}">
TreeListView.ItemTemplate>
HierarchicalDataTemplate ItemsSource="{Binding Collection, IsAsync=True}" />
TreeListView.ItemTemplate>
TreeListView.View>
GridView>
GridViewColumn CellTemplate="{StaticResource ExpandCellTemplate}" Header="Property1" />
GridViewColumn DisplayMemberBinding="{Binding Property2}" Header="Property2" />
GridViewColumn DisplayMemberBinding="{Binding Property3}" Header="Property3" />
GridViewColumn DisplayMemberBinding="{Binding Property4}" Header="Property4" />
GridViewColumn DisplayMemberBinding="{Binding Property5}" Header="Property5" />
GridViewColumn DisplayMemberBinding="{Binding Property6}" Header="Property6" />
GridViewColumn DisplayMemberBinding="{Binding Property7}" Header="Property7" />
GridViewColumn DisplayMemberBinding="{Binding Property8}" Header="Property8" />
GridViewColumn DisplayMemberBinding="{Binding Property9}" Header="Property9" />
GridViewColumn DisplayMemberBinding="{Binding Property10}" Header="Property10" />
GridViewColumn DisplayMemberBinding="{Binding Property11}" Header="Property11" />
GridViewColumn DisplayMemberBinding="{Binding Property12}" Header="Property12" />
GridView>
TreeListView.View>
TreeListView>
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
ngIf 是 Angular 的行为指令。 基本写法:{{ hero.name }} 当 ngIf 表达式求值为 truthy 时,Angular会渲染在then子句中提供的模板;当为falsy 时,Angular会渲染服务器托管网在可选的else子句中提供的…