前言

  本文将探讨如何利用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. 我的小程序之旅八:基于weixin-java-mp实现微信公众号被动回复消息

    在微信里有这样一个公众号[华为运动健康],当点击最新排行的时候,公众号就会发送今天最新的运动步数给你.如下图: 这里有两种格式的消息 1.有头像框,有聊天框--普通消息 2.消息有样式.颜色等--模板 ...

  2. 【Android逆向】算法还原2

    这题比较简单 1. app-release.apk 安装至手机 提示需要输入账号和密码 2. jadx 打开看看 public native boolean check(byte[] bArr, by ...

  3. 用Taro写一个微信小程序——版本升级

    一.升级 1.升级Taro CLI至最新版本 taro update self npm i -g @tarojs/cli 2.更新项目中 Taro 相关的依赖 taro update project ...

  4. vscode自定义运行和调试创建launch.json文件及项目独立配置文件

    1.创建lauch.json文件 2.然后在项目目录中会自动创建.vscode的目录 3.在.vscode目录下创建settings.json项目独立配置文件 4.在settings.json中写入 ...

  5. 【LeetCode数组#2双指针法】移除元素、删除有序数组中的重复项、移动0

    移除元素 力扣27题目链接(opens new window) 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度. 不要使用额外的数组 ...

  6. C#的Winform程序关于单击和双击的区别 - 开源研究系列文章

    前些天编码的时候有个关于应用程序的托盘图标的鼠标Mouse Down里的单击和双击的问题,只是想单击的时候显示主窗体,双击的时候显示操作窗体.但是编码并调试的时候发现Windows的鼠标双击的事件先执 ...

  7. 图数据库 Nebula Graph 的代码变更测试覆盖率实践

    对于一个持续开发的大型工程而言,足够的测试是保证软件行为符合预期的有效手段,而不是仅仅依靠 code review 或者开发者自己的技术素质.测试的编写理想情况下应该完全定义软件的行为,但是通常情况都 ...

  8. 五: Mysql权限管理

    # 权限管理 关于MySQL的权限简单的理解就是MySQL允许你做你权力以内的事情,不可以越界.比如只允许你执行SELECT操 作, 那么你就不能执行UPDATE操作.只允许你从某台机器上连接MySQ ...

  9. Java 多线程------多线程的创建(2),方式一:继承于Thread类

    1 package com.bytezero.threadexer; 2 3 /** 4 * 创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数 5 * 6 * 7 * ...

  10. Ubuntu 离线安装软件包

    Ubuntu 离线安装软件包 关键词:apt-offline,Ubuntu,dpkg,.deb 本文使用的ubuntu20.04,当机器无法连接外网时,我们使用离线的方式安装软件包. 离线安装的软件包 ...