前言

  本文将探讨如何利用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>
<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)的更多相关文章

  1. WPF Step By Step 控件介绍

    WPF Step By Step 控件介绍 回顾 上一篇,我们主要讨论了WPF的几个重点的基本知识的介绍,本篇,我们将会简单的介绍几个基本控件的简单用法,本文会举几个项目中的具体的例子,结合这些 例子 ...

  2. 实用的树形菜单控件tree

     jQuery plugin: Treeview  这个插件能够把无序列表转换成可展开与收缩的Tree. jQuery plugin: Treeview  jQuery  jstree  jsTree ...

  3. ExtJS4.2学习(10)分组表格控件--GroupingGrid(转)

    鸣谢网址:http://www.shuyangyang.com.cn/jishuliangongfang/qianduanjishu/2013-11-17/179.html ------------- ...

  4. html树形菜单控件

    html树形菜单控件  链接 http://www.ithao123.cn/content-713974.html         jQuery plugin: Treeview  这个插件能够把无序 ...

  5. 在GridControl表格控件中实现多层级主从表数据的展示

    在一些应用场景中,我们需要实现多层级的数据表格显示,如常规的二级主从表数据展示,甚至也有多个层级展示的需求,那么我们如何通过DevExpress的GridControl控表格件实现这种业务需求呢?本篇 ...

  6. Atitit  项目界面h5化静态html化计划---vue.js 把ajax获取到的数据 绑定到表格控件 v2 r33.docx

    Atitit  项目界面h5化静态html化计划---vue.js 把ajax获取到的数据 绑定到表格控件 v2 r33.docx 1. 场景:应用在项目列表查询场景下1 1.1. 预计初步掌握vue ...

  7. 【转】html树形菜单控件

    Query plugin: Treeview  这个插件能够把无序列表转换成可展开与收缩的Tree. 主页:http://bassistance.de/jQuery-plugins/jquery-pl ...

  8. 【案例分享】在 React 框架中使用 SpreadJS 纯前端表格控件

    [案例分享]在 React 框架中使用 SpreadJS 纯前端表格控件 本期葡萄城公开课,将由国电联合动力技术有限公司,资深前端开发工程师——李林慧女士,与大家在线分享“在 React 框架中使用 ...

  9. 如何在web中实现类似excel的表格控件

    Execl功能非常强大,内置的很多函数或公式可以大大提高对数据的加工处理能力.那么在web中有没有类似的控件呢?经过一番搜寻,发现handsontable具备了基本的excel功能支持公式,同时能对数 ...

  10. 在WPF中使用WinForm控件方法

    1.      首先添加对如下两个dll文件的引用:WindowsFormsIntegration.dll,System.Windows.Forms.dll. 2.      在要使用WinForm控 ...

随机推荐

  1. std::vector<std::string> 与 char** 的转换

    vecotr 容器中插入多条字符串,再将 vector 的地址转换为 char** 代码如下: #include <stdio.h> #include <string> #in ...

  2. xcode真机运行包添加(更新到15.4)

    今天给真机装包,提示xcode版本低不能安装,电脑内存有些不足,xcode更新不了,这时候了解到xcode可以单独添加开发包\(^o^)/~ 1.下载 链接: https://pan.baidu.co ...

  3. 如何渲染最原始的yuv视频数据?

    一.整体思路 我们在用纹理增加细节那篇文章中提到过,要将图片渲染在屏幕上,首先要拿到图片的像素数组数据,然后将像素数组数据通过纹理单元传递到片段着色器中,最后通过纹理采样函数将纹理中对应坐标的颜色值采 ...

  4. 【转载】nltk英文自定义分词

    NLTK项目地址: https://github.com/nltk/nltk_data/tree/gh-pages/packages NLTK基础分词用例: https://www.cnblogs.c ...

  5. 苹果工程师对iOS线程开发的那点事津津乐道

    pthread,Thread总结 pthread: 通用的多线程API 使用方法 // 1. 创建线程: 定义一个pthread_t类型变量 pthread_t thread; // 2. 开启线程: ...

  6. redis-cluster 性能调优

    关闭RDB防止fork进程的内存溢出问题 save "" appendonly=yes 防止某个节点挂掉,整个cluster挂掉的问题 cluster-require-full-c ...

  7. 【Azure 环境】当在Azure 环境中调用外部接口不通时,如何定位SSL Certificate Problem

    问题描述 如果在Azure VM中,发现同一个API,一台VM可以访问成功,另外一台访问失败.如何来调试并定位问题呢? 问题分析 第一步,查看访问外部API不通时候出现什么错误.如果没有明确的错误消息 ...

  8. expect tcl 摘录

    目录 部分参考来源说明 例子 expect命令 核心命令有三个 spawn.expect.send 其他expect命令 expect命令的选项 变量 tcl摘录 数据类型 符号 命令 其他说明 部分 ...

  9. RocketMQ—RocketMQ集成SpringBoot

    RocketMQ-RocketMQ集成SpringBoot 新建生产者的boot项目和消费者的boot项目,pom文件重点如下: <dependencies> <dependency ...

  10. opencv库图像基础1-python

    opencv库图像基础-python 基本操作 图片颜色通道 非灰度图的颜色通道是红绿蓝,在opencv中默认是BGR的顺序 argparse模块 argparse 库是 Python 标准库中用于命 ...