WPF实现树形表格控件(TreeListView)

  • WPF实现树形表格控件(TreeListView)已关闭评论
  • 17 次浏览
  • A+
所属分类:.NET技术
摘要

前言   本文将探讨如何利用WPF框架实现树形表格控件,该控件不仅能够有效地展示复杂的层级数据,还能够提供丰富的个性化定制选项。我们将介绍如何使用WPF提供的控件、模板、布局、数据绑定等技术来构建这样一个树形表格。

前言

  本文将探讨如何利用WPF框架实现树形表格控件,该控件不仅能够有效地展示复杂的层级数据,还能够提供丰富的个性化定制选项。我们将介绍如何使用WPF提供的控件、模板、布局、数据绑定等技术来构建这样一个树形表格。

一、运行效果

1.1默认样式

WPF实现树形表格控件(TreeListView)

1.2 自定义样式

WPF实现树形表格控件(TreeListView)

二、代码实现

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

 

 技术交流群
WPF实现树形表格控件(TreeListView)
 
 联系方式
WPF实现树形表格控件(TreeListView)