简介这是一份面向 WPF 开发者的树形表格TreeGrid实现示例基于 C# 与 XAML 编写适合需要在桌面端展示层级数据、又不想引入第三方商业控件的开发者参考。资源源自 GitHub 开源项目作者对其进行了整理打包可用于学习树状表格的布局、节点展开折叠与数据绑定等核心思路。压缩包共 36 个文件约 66KB其中以 12 个 cs 源码文件为主配合 2 个 xaml 界面文件、csproj 工程文件及 dll、resources、resx 等资源另有 exe、pdb 等编译产物结构完整可直接在 Visual Studio 中打开运行。内容涵盖主窗口、层级缩进转换器、程序集信息与资源配置等模块便于读者理解树形表格从界面到逻辑的组织方式。目前已有 272 人学习下载适合具备一定 WPF 基础、希望快速掌握树状表格实现技巧的开发者参考借鉴。1. 从 treegrid.zip 说起WPF 里那棵能编辑的树状表格到底怎么落地手上拿到一个叫treegrid.zip的包解压出来是 WPF 的 C# 工程核心就一件事把树形结构和表格合到一张控件里。普通DataGrid只能平铺行TreeView只能展开节点可业务里大量场景要的是「左边能折叠、右边能编辑」——比如物料 BOM、组织架构带编制人数、设备台账带实时状态。这就是 treeGrid 在 WPF 里的真实需求一棵可展开的树每一行还带多列数据列能排序、能编辑、能绑定。热搜里wpf、c#、treegrid反复出现说明搜的人多半卡在同一个地方WPF 原生没有 TreeGrid 控件第三方库要么收费要么风格不搭自己拼又不知道从哪下手。这篇就按我实际做过的路子从控件选型讲到treegrid.zip这类工程怎么跑起来、参数怎么调、坑在哪。适合已经会写 WPF 窗口、能看懂 XAML 绑定但没做过树状表格的 C# 开发者新手照着步骤也能复现熟手可以直接跳到参数和避坑那两章。2. 选型先立住WPF 做 treeGrid 的三条路和各自代价动手之前先把路线定死不然写到一半发现控件不支持编辑返工成本很高。WPF 里实现树状表格业内常见就三条路我按「改动量 / 可控性 / 长期维护」三个维度拆开讲。2.1 原生组合TreeView 套 DataGrid 的模板方案最常见的做法是把DataGrid的某一行模板里塞一个TreeView或者反过来用TreeView的ItemTemplate里放一个横向排列的字段区。核心思路是让层级缩进和列对齐同时成立。!-- 用 HierarchicalDataTemplate 让 TreeView 的每一项右侧带多列 -- TreeView ItemsSource{Binding Roots} TreeView.ItemTemplate HierarchicalDataTemplate ItemsSource{Binding Children} Grid Grid.ColumnDefinitions ColumnDefinition Width200/ ColumnDefinition Width100/ ColumnDefinition Width120/ /Grid.ColumnDefinitions TextBlock Grid.Column0 Text{Binding Name}/ TextBlock Grid.Column1 Text{Binding Code}/ TextBlock Grid.Column2 Text{Binding Status}/ /Grid /HierarchicalDataTemplate /TreeView.ItemTemplate /TreeView这段的关键在HierarchicalDataTemplate的ItemsSource指向子集合Grid负责把一行拆成多列。逻辑上它确实能显示树状多列但问题也明显列宽靠手写死没法像DataGrid那样拖拽调整、点表头排序编辑要自己往模板里塞TextBox并处理提交。参数上Width写死是硬伤实际项目里我会改成共享Grid.ColumnDefinitions或者干脆放弃这条路。提示这条路只适合列固定、不需要排序和编辑的展示型场景一旦业务要「改一个单元格的值回写数据库」维护量会迅速失控。2.2 第三方控件HandyControl、DevExpress 这类库的 TreeGrid热搜里出现了wpf handycontrol说明不少人已经在用国产开源控件库。HandyControl 本身偏基础控件美化真正带 TreeGrid 的更多是 DevExpress、Telerik 这类商业库或者开源里的TreeListView实现。选它的理由是省事排序、编辑、虚拟化、拖拽列宽全都现成。代价是学习成本和授权。商业库要钱开源库文档参差。我一般评估三点控件是否支持ObservableCollection的层级绑定、是否支持单元格级编辑回写、虚拟化在几千行时会不会卡。参数上重点看VirtualizingPanel.IsVirtualizing和VirtualizationMode树状结构一旦节点多不开虚拟化滚动就是灾难。2.3 自绘 TreeGrid继承 Control 或拼 ItemsControl第三条路是自己写一个TreeGrid : Control内部用ItemsControl递归渲染配合GridViewRowPresenter做列对齐。这条路可控性最高treegrid.zip这类工程多半就是这种自绘或半自绘方案。核心是把「层级」和「列」两套布局解耦层级用缩进Margin控制列用统一的列定义集合驱动。// 列定义集合供自绘 TreeGrid 统一渲染表头和单元格 public class TreeGridColumn { public string Header { get; set; } // 表头文字 public string BindingPath { get; set; } // 绑定到数据源的哪个属性 public double Width { get; set; } 120;// 默认列宽 public bool IsEditable { get; set; } // 该列是否可编辑 }BindingPath决定这一列取数据对象的哪个字段Width是默认宽度IsEditable控制编辑开关。自绘方案的好处是列定义集中管理改一处全表生效坏处是排序、编辑、键盘导航这些都要自己补工作量不小。我的经验是列少、交互简单就自绘列多、要排序编辑就上成熟控件别硬扛。3. 把 treegrid.zip 跑起来环境、绑定和最小可运行工程路线定了自绘或半自绘之后接下来是让工程真正跑起来。这一章按「建工程 → 定义数据模型 → 绑定层级 → 渲染列」的顺序走每步都给可抄的代码。3.1 环境准备和工程结构先确认环境Visual Studio 2022、.NET 6 或 .NET Framework 4.8 都行WPF 项目模板选「WPF 应用」。treegrid.zip这类包解压后一般有.sln、主工程、可能还有一个控件库工程。打开.sln先还原 NuGet 包再编译。如果报找不到某个控件命名空间多半是缺了对应的 NuGet 引用按packages.config或.csproj里的PackageReference补上。工程结构上我习惯分三层Models放数据模型Controls放自绘 TreeGridViewModels放绑定逻辑。这样列定义、数据、渲染互不干扰后面加列或改绑定不用动控件代码。3.2 数据模型带子集合的节点类树状表格的数据模型必须能表达层级最常见就是每个节点带一个Children集合。public class TreeNode : INotifyPropertyChanged { public string Name { get; set; } // 节点名称第一列 public string Code { get; set; } // 编码第二列 private string _status; public string Status // 状态列改值要通知 UI { get _status; set { _status value; OnPropertyChanged(nameof(Status)); } } public ObservableCollectionTreeNode Children { get; set; } new(); public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string name) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }Children用ObservableCollection是为了增删子节点时 UI 自动刷新。Status实现了INotifyPropertyChanged因为它是可编辑列改完要立刻反映到界面。如果所有列都只读可以省掉通知逻辑但只要有一列要编辑就必须加否则会出现「数据改了界面不动」的玄学问题。3.3 层级绑定HierarchicalDataTemplate 的正确写法绑定的核心是让控件知道「子节点从哪来」。用HierarchicalDataTemplate时ItemsSource指向Children模板内容决定一行长什么样。Window.Resources HierarchicalDataTemplate DataType{x:Type local:TreeNode} ItemsSource{Binding Children} StackPanel OrientationHorizontal TextBlock Text{Binding Name} Width200/ TextBlock Text{Binding Code} Width100/ TextBox Text{Binding Status, UpdateSourceTriggerPropertyChanged} Width120/ /StackPanel /HierarchicalDataTemplate /Window.ResourcesDataType指定这个模板作用于哪种类型ItemsSource绑定子集合StackPanel横向排列各列。UpdateSourceTriggerPropertyChanged是关键参数默认TextBox是失焦才回写改成PropertyChanged后每敲一个字就更新数据源编辑体验才跟得上。代价是频繁触发通知数据量大时要注意性能。3.4 列渲染用共享列宽对齐表头和内容自绘方案里最容易翻车的是表头和内容对不齐。解决办法是把列宽定义抽出来表头和每一行都引用同一份。// 共享列定义表头和行都从这里取宽度 public static class ColumnDefs { public static readonly double[] Widths { 200, 100, 120 }; }XAML 里表头和行模板都按这个数组设Width改一处两边同步。如果列宽要支持拖拽就得把Widths换成可通知的属性并在拖拽结束时回写。这一步不做用户拖了表头列宽、内容不跟着变体验直接崩。4. 参数与交互调优编辑、排序、虚拟化三个必调点工程能跑只是起点真正决定好不好用的是编辑回写、排序和虚拟化这三块。这一章逐个拆参数和实现。4.1 单元格编辑从只读到可改的回写链路可编辑列要打通「UI 输入 → 数据源 → 持久化」整条链路。绑定层用UpdateSourceTriggerPropertyChanged保证实时回写数据层用INotifyPropertyChanged保证界面刷新持久化层在属性 setter 里或统一提交时落库。public string Status { get _status; set { if (_status value) return; // 值没变不触发避免无谓刷新 _status value; OnPropertyChanged(nameof(Status)); MarkDirty(); // 标记该节点已修改供批量保存 } }if (_status value) return;这行是血泪经验不加的话某些绑定场景会形成「改值 → 通知 → 再改值」的循环界面卡死。MarkDirty用来收集脏节点保存时只提交改过的比全量提交高效得多。4.2 排序点表头对树状结构排序的边界平铺表格排序简单树状表格排序麻烦在「排的是同级还是全树」。常见做法是只对同一父节点下的兄弟节点排序保持层级不乱。// 对某个节点的子集合按指定字段排序 public static void SortChildren(TreeNode node, string field, bool asc) { var sorted asc ? node.Children.OrderBy(c GetValue(c, field)).ToList() : node.Children.OrderByDescending(c GetValue(c, field)).ToList(); node.Children.Clear(); foreach (var c in sorted) node.Children.Add(c); }GetValue用反射按字段名取值OrderBy/OrderByDescending控制升降序。注意Clear再Add会触发多次集合变更通知节点多时建议用支持批量更新的集合或先挂起通知。排序字段是字符串时中文排序要指定StringComparer否则按 Unicode 码点排结果不符合直觉。4.3 虚拟化几千行不卡的开关和参数树状表格行数一多不开虚拟化滚动就卡。WPF 的虚拟化对平铺列表支持好对树状结构要额外配置。TreeView VirtualizingPanel.IsVirtualizingTrue VirtualizingPanel.VirtualizationModeRecycling ScrollViewer.CanContentScrollTrueIsVirtualizing打开虚拟化VirtualizationModeRecycling复用容器而不是每次重建滚动更顺。CanContentScrollTrue让滚动按项而不是按像素虚拟化才生效。这三个参数缺一个虚拟化都可能不工作。注意树状结构展开全部节点时虚拟化效果会打折因为展开的节点都要参与布局实际项目里我会限制默认展开层级。5. 避坑与排查treeGrid 在 WPF 里最容易翻车的五件事这一章全是踩过的坑按「现象 → 原因 → 解决」写遇到对应症状直接对号入座。5.1 展开节点后界面卡死几秒现象节点一多点展开要等好几秒才响应。原因Children集合在展开时一次性创建大量子对象且没开虚拟化布局一次性算完。解决开虚拟化三参数子集合改成懒加载——只在展开时才去查数据库或构造子节点别在初始化时把整棵树建出来。5.2 编辑单元格后数据没保存现象界面上改了值切走再切回来变回原样。原因绑定没设UpdateSourceTriggerPropertyChanged或者数据模型没实现INotifyPropertyChanged又或者改了没调保存。解决先确认绑定参数再确认模型通知最后确认脏数据有没有提交。三步逐一排查别一上来就怀疑控件。5.3 表头和内容列对不齐现象表头三列内容行错位半列。原因表头和行用了两套独立的列宽定义或者行模板里混了Margin导致实际宽度偏移。解决抽共享列定义表头和行都引用同一份检查行模板有没有多余的Padding、Margin缩进用的Margin要算进第一列宽度里。5.4 中文排序结果不对现象按名称排序结果顺序看着乱。原因默认字符串比较按 Unicode 码点中文不适用。解决排序时传StringComparer.CurrentCulture或StringComparer.Create(new CultureInfo(zh-CN), false)让比较器按中文习惯排。5.5 大量节点时内存持续上涨现象反复展开折叠内存只涨不降。原因每次展开都新建子对象旧对象没释放或者事件订阅没取消节点被事件持有无法回收。解决子节点缓存复用别每次重建节点销毁时取消事件订阅用内存分析工具确认是不是绑定泄漏。6. 进阶把 treeGrid 做成可复用控件并验证它真的对做到能跑、能编辑、不卡之后下一步是把它抽成可复用控件并且有一套验证方法确认它没坏。这一章讲封装技巧和验证手段。封装的核心是把「列定义」和「数据源」做成依赖属性让外部只传数据、不碰内部渲染。public class TreeGridControl : Control { public static readonly DependencyProperty ColumnsProperty DependencyProperty.Register(nameof(Columns), typeof(ObservableCollectionTreeGridColumn), typeof(TreeGridControl), new PropertyMetadata(null)); public static readonly DependencyProperty ItemsSourceProperty DependencyProperty.Register(nameof(ItemsSource), typeof(IEnumerable), typeof(TreeGridControl), new PropertyMetadata(null)); public ObservableCollectionTreeGridColumn Columns { get (ObservableCollectionTreeGridColumn)GetValue(ColumnsProperty); set SetValue(ColumnsProperty, value); } public IEnumerable ItemsSource { get (IEnumerable)GetValue(ItemsSourceProperty); set SetValue(ItemsSourceProperty, value); } }Columns和ItemsSource做成依赖属性后XAML 里就能像用原生控件一样绑定外部完全不用关心内部怎么渲染。参数上PropertyMetadata可以带默认值和变更回调列集合变了自动重绘。验证方面我一般做三层单元测试测数据模型的排序和脏标记逻辑UI 测试用自动化工具模拟展开、编辑、保存确认回写正确性能测试灌几千个节点量展开耗时和内存。下面这张表是我常用的验证清单。验证项方法通过标准层级绑定构造三层节点检查展开子节点正确显示编辑回写改单元格读数据源值同步更新排序点表头检查同级顺序顺序符合预期虚拟化灌 5000 节点滚动无明显卡顿内存反复展开折叠 100 次内存稳定不持续涨最后说个我自己的习惯每次改完控件先拿一个只有三层、每层三个节点的小数据集跑一遍确认基本交互没坏再上大数据集压测。小数据集能快速定位逻辑错误大数据集才暴露性能问题顺序反了会浪费很多时间在性能调优上结果发现是绑定写错了。这套 treeGrid 方案值不值得做取决于你的业务是不是真的需要「树 表」同时成立——如果只是展示层级TreeView 够了如果只是平铺数据DataGrid 够了只有当两者都要才值得投入把 treeGrid 做扎实。希望帮到你。本文还有配套的精品资源点击获取