尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

DataGridView 树形表格实现:自绘缩进与展开折叠全攻略

DataGridView 树形表格实现:自绘缩进与展开折叠全攻略 简介针对 Windows 窗体应用开发中需要以树形结构展示层次化数据的常见需求这份资源包提供了一套基于 C# 与 WinForms 的完整实现方案。示例工程在 Visual Studio 2012 中编写通过自定义树节点模型、扩展表格控件的事件处理、重绘展开与折叠图标、动态控制列显隐等手段使原本只能呈现二维表格的 DataGridView 控件具备清晰的层级浏览体验适合已经熟悉基础控件用法、希望在项目中加入树形列表交互的.NET窗体开发者参考学习。整个压缩包共包含 32 个文件主要分为 10 个 C# 源代码文件、3 个应用配置文件、3 个可直接运行的可执行程序、2 个资源文件以及解决方案文件、项目文件和少量调试缓存文件资源压缩后仅 68KB结构简明便于快速下载和查阅。目前已有 1948 人浏览学习。配套的 grid_test 项目演示了从树形数据模型建立、数据源绑定、单元格外观定制到节点展开折叠事件处理的完整链路。读者可以在 Visual Studio 中直接打开并调试逐步观察每一步的界面变化理解父节点行与子节点行的显示逻辑并将其中的思路迁移到自己的数据展示模块中。 家里旧电脑翻出一堆 Delphi 写的管理系统其中不少列表界面都用到了 TDBGrid 或者 TStringGrid。Delphi 原生的 TTreeView 单独用没问题可一旦要求表格里同时展示多列明细和层级关系就有点捉襟见肘了。最近刚好又接到类似需求——要在表格控件里直接展示树形层级同时保留列排序、列宽调整、单元格编辑这些表格特性。网上搜了一圈大部分方案都是针对 .NET WinForms 的 DataGridView和 Delphi 的 TDBGrid 场景不太一样但思路倒是相通的本质上都要解决“层级缩进”“展开折叠”“节点图标”这三件事。这篇文章我把 DataGridView 显示树结构的几种主流做法、底层原理和实际踩坑记录整理出来Delphi 环境下用 TDBGrid 模拟树的思路也会一并聊到给遇到同样需求的朋友一个完整的参考。1. 为什么 DataGridView 原生不支持树先搞清楚表格控件的边界很多刚接触这个需求的人第一反应是DataGridView 不是支持 RowTemplate 吗直接塞一个 TreeView 进去不就行了真这么干过的人都知道问题远没有这么简单。DataGridView 的设计模型是“平面二维表”每一行对应一条数据记录每一列对应一个字段。而树结构的本质是“父子嵌套关系”两者在数据模型上就是冲突的。打个比方DataGridView 像一张 Excel 工作表每个单元格彼此独立TreeView 像资源管理器左侧的目录树节点天然带有父子层级。想在表格里显示树本质上不是“给表格加树”而是“把树拍扁成表格再用视觉手段把层级感还原回来”。所以关键不在控件本身而在数据层的组织方式。1.1 先理解 DataGridView 的数据绑定机制DataGridView 支持三种数据来源直接绑定 DataTable、绑定 BindingSource、或者完全手动操作 Rows 集合。要在里面实现树形效果推荐的做法是最终手动控制 Rows因为树形结构每一行的位置是动态计算的直接绑定 DataTable 时很难控制行的插入顺序和缩进状态。我常用的做法是先把树形数据源扁平化成一个 DataTable每一行额外携带几个隐藏字段比如 ParentId、Level、IsLeaf、ExpandState然后再根据这些字段在 DataGridView 中动态生成行。扁平化这一步听起来简单实际处理时坑很多后面专门讲。1.2 对比三种常见实现路线的优劣第一种是“TreeView DataGridView 并排联动”左边树选节点右边表格显示对应数据。这种方案实现最简单但用户体验上表格始终是平面的层级感不强严格来说不算“表格显示树”。第二种是“自绘缩进 展开折叠按钮”核心思路是给 DataGridView 添加一个专门的树列通过 CellPainting 事件绘制缩进线和加减号按钮点击时动态增删行。这种做法最接近原生树形表格的体验也是本文重点展开的方案。第三种是“第三方控件或商业组件”比如 DevExpress 的 TreeList、ComponentOne 的 FlexGrid。功能最全性能最好但需要引入额外依赖有的还要授权费。如果项目预算紧张或者不想引入重型依赖自绘方案完全够用。2. 自绘树形表格的核心设计数据扁平化与行状态管理自绘方案的第一步也是最重要的一步就是设计好树形数据和扁平化表格行之间的映射关系。否则后面所有绘制逻辑都会变得混乱不堪。2.1 树形数据源如何设计我这里以最常见的场景为例有一个部门表字段包括 DeptId、ParentId、DeptName、MemberCount。要求是在 DataGridView 中显示部门层级同时显示每个部门的成员数。先定义一个简单的树节点类这类代码几乎所有项目里都能复用public class TreeNodeItem { public string Id { get; set; } public string ParentId { get; set; } public string Name { get; set; } public int MemberCount { get; set; } public int Level { get; set; } public bool IsLeaf { get; set; } public bool IsExpanded { get; set; } public ListTreeNodeItem Children { get; set; } new ListTreeNodeItem(); }然后从数据库加载数据后先构建一棵内存树再按“展开的节点才显示子节点”的规则把树拍平成 Listprivate ListTreeNodeItem _flatList new ListTreeNodeItem(); private void FlattenTree(ListTreeNodeItem nodes, int level) { foreach (var node in nodes) { node.Level level; _flatList.Add(node); if (node.IsExpanded node.Children.Count 0) { FlattenTree(node.Children, level 1); } } }注意这里的 IsExpanded 字段它决定了节点是否展开。每次点击展开折叠按钮时只需要修改这个字段然后重新调用 FlattenTree最后刷新 DataGridView 的 Rows 即可。2.2 行状态与数据行如何关联扁平化列表生成后DataGridView 的每一行都对应 _flatList 中的一项。行号和数据项的映射关系是稳定的只要在刷新时重新绑定就不会错位。这里有一个关键点容易被忽略每次展开或折叠后受影响的不只是当前节点而是其后面所有兄弟节点的行号。比如根节点有 10 个子节点展开第 3 个节点后第 4 到第 10 个根节点在表格中的位置会整体下移因为它们中间插入了新行。所以不要试图手动计算行号变化最稳妥的方式是每次状态变更后全量重建 Rows。数据量在几千行以内时速度完全可接受。2.3 树列与非树列的分工在设计列的阶段就想清楚哪一列是树列哪一列是普通列。树列一般放第一列或者紧挨着第一列用来显示缩进、展开按钮和节点文字。其他列正常绑定数据字段即可。以部门表为例列结构设计如下列标题数据字段是否树列说明部门名称Name是显示缩进和展开按钮成员数MemberCount否普通显示负责人Manager否普通显示树列的实际宽度建议比正常的名称列宽 20 到 30 像素因为要给展开按钮留出空间。缩进量我一般按每个层级 18 到 22 像素计算太窄了层级感不明显太宽了文字很容易被截断。3. 核心绘制细节CellPainting 事件里的展开按钮与缩进逻辑数据层准备好之后视觉呈现就是重头戏了。DataGridView 的单元格绘制全部集中在 CellPainting 事件中这也是自绘树形表格最核心的代码区域。3.1 缩进绘制简单但容易搞错的坐标计算缩进的核心是把文字起始位置向右偏移偏移量 层级 × 每级缩进像素。但注意DataGridView 的单元格绘制区域是整个单元格矩形。如果直接修改文字绘制位置普通的 CellStyle 设置就不生效了需要在绘制文字时手动指定 x 坐标private void dgv_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) { if (e.ColumnIndex treeColumnIndex e.RowIndex 0) { var node _flatList[e.RowIndex]; int indent node.Level * 20; // 先绘制默认背景 e.PaintBackground(e.ClipBounds, true); // 绘制展开/折叠按钮 if (!node.IsLeaf) { Rectangle btnRect new Rectangle(e.CellBounds.X indent - 12, e.CellBounds.Y (e.CellBounds.Height - 12) / 2, 12, 12); DrawExpandButton(e.Graphics, btnRect, node.IsExpanded); indent 16; } // 绘制连接线如果需要 if (showTreeLines) { DrawTreeLines(e.Graphics, e.CellBounds, node.Level, indent); } // 绘制节点文本 TextRenderer.DrawText(e.Graphics, node.Name, e.CellStyle.Font, new Point(e.CellBounds.X indent, e.CellBounds.Y 3), e.CellStyle.ForeColor); e.Handled true; } }反复调试后发现文字垂直位置不能直接用 e.CellBounds.Y因为 DataGridView 默认的单元格内边距会影响绘制效果。最保险的做法是 y 坐标取e.CellBounds.Y 3这样与默认单元格文本的垂直位置基本一致。如果你想用标准的 ContentAlignment 对齐方式可以在绘制文字时传入一个 Rectangle然后用 TextRenderer 的 TextFormatFlags 控制水平垂直居中。3.2 展开按钮的绘制三角箭头还是方框符号展开按钮我推荐画成三角形箭头因为与 TreeView 原生的视觉风格最接近。三角形的绘制用 GraphicsPath 就很容易private void DrawExpandButton(Graphics g, Rectangle rect, bool isExpanded) { g.SmoothingMode System.Drawing.Drawing2D.SmoothingMode.AntiAlias; using (var path new System.Drawing.Drawing2D.GraphicsPath()) { if (isExpanded) { // ▼ 向下三角形 path.AddPolygon(new Point[] { new Point(rect.X 2, rect.Y 3), new Point(rect.X rect.Width - 2, rect.Y 3), new Point(rect.X rect.Width / 2, rect.Y rect.Height - 3) }); } else { // ▶ 向右三角形 path.AddPolygon(new Point[] { new Point(rect.X 3, rect.Y 2), new Point(rect.X 3, rect.Y rect.Height - 2), new Point(rect.X rect.Width - 3, rect.Y rect.Height / 2) }); } using (var brush new SolidBrush(Color.Gray)) { g.FillPath(brush, path); } } }三角形大小和单元格高度需要匹配我一般画 10 到 12 像素的三角形。如果单元格高度是默认的 22 像素这个尺寸正好如果是自增行高就得按比例调整。3.3 图标和选中状态的坑为了让树形表格更好看很多人会给非叶节点加一个文件夹图标叶节点加一个文件图标。用 DataGridView 自绘时有个坑如果你在 CellPainting 里直接绘制了图标那么单元格选中时的背景颜色就看不见了因为整个绘制都被 e.Handled true 拦截了。解决办法是选中状态的绘制也自己处理。比如判断 e.State 是否包含 DataGridViewElementStates.Selected如果有就画一个系统的高亮背景色否则画默认白色Color bgColor (e.State DataGridViewElementStates.Selected) DataGridViewElementStates.Selected ? dgv.DefaultCellStyle.SelectionBackColor : dgv.DefaultCellStyle.BackColor;图标绘制我自己通常只保留在树列上其他列保持原生绘制。这样做的好处是代码逻辑简单而且不会出现选中状态颜色不一致的问题。如果你的图标资源比较多也可以把所有列都自绘但每一列都要画背景和文字工作量会翻倍而且维护起来非常痛苦。4. 展开折叠交互命中测试与点击区域的边界判断动态绘制只是视觉层面的事真正的交互逻辑在于点击处理。展开折叠按钮只有在用户点击那个小三角时才应该触发点击文字部分则不应有任何反应或者选中该行。这里需要注意的坑是点击区域的坐标计算必须和绘制时完全一致否则会出现“画在 A 处、点在 B 处”的错位问题。4.1 用 CellClick 还是 CellMouseClick常见做法是处理 CellClick 事件但这个事件在用户点击单元格的非文本区域时也可能触发而且 CellClick 拿到的坐标是单元格坐标还需要额外转换。我实际用下来更推荐 CellMouseClickprivate void dgv_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e) { if (e.Button ! MouseButtons.Left) return; if (e.ColumnIndex ! treeColumnIndex || e.RowIndex 0) return; var node _flatList[e.RowIndex]; if (node.IsLeaf) return; int indent node.Level * 20; // 和绘制时完全相同的按钮矩形计算逻辑 Rectangle btnRect new Rectangle( dgv.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, true).X indent - 12, dgv.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, true).Y (dgv.Rows[e.RowIndex].Height - 12) / 2, 12, 12); if (btnRect.Contains(e.Location)) { node.IsExpanded !node.IsExpanded; ReloadTreeGrid(); } }注意GetCellDisplayRectangle(columnIndex, rowIndex, true)的第三个参数是 cutOverflow传 true 表示返回单元格的可见部分这个在存在水平滚动条时尤其重要。4.2 命中测试偏移问题的根源按钮点击错位的现象绝大多数出在缩进坐标和滚动偏移的计算上。CellMouseClick 的 e.Location 坐标是相对于当前单元格的而不是相对于 DataGridView 整个客户区。而 GetCellDisplayRectangle 返回的 X 是绝对坐标。如果直接用 e.Location 和按钮绝对坐标比较当存在横向滚动时必然错位。正确的思路是e.Location 对应的是单元格内部的相对坐标所以按钮矩形也应该转换为相对坐标。也就是 GetCellDisplayRectangle 返回的 X 和 Y 都要减去单元格本身的 X、YRectangle cellRect dgv.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, true); Rectangle btnRect new Rectangle( cellRect.X indent - 12 - cellRect.X, // 等价于 indent - 12 cellRect.Y (dgv.Rows[e.RowIndex].Height - 12) / 2 - cellRect.Y, 12, 12);这样按钮的相对 X 就只是缩进量减 12。很多人没转过这个弯所以总是出现“按钮画在左边却要点右边才能展开”的诡异问题。4.3 双击展开与右键菜单扩展除了点小三角展开很多用户习惯双击节点文字展开。这个交互可以作为可选增强。我的做法是在 CellDoubleClick 事件中判断当前列是否是树列如果是就切换展开状态。但要小心如果该行本身绑定的事件逻辑和展开有冲突就需要额外判断。右键菜单也是树形表格的常见需求比如“新增子节点”“删除节点”“刷新”。DataGridView 本身支持 ContextMenuStrip直接绑定即可。但要注意右键点击时不会触发 CellMouseClick 的左键过滤逻辑所以需要在 CellMouseDown 事件里判断右键并记录当前行然后再设置右键菜单private void dgv_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e) { if (e.Button MouseButtons.Right e.RowIndex 0) { dgv.CurrentCell dgv.Rows[e.RowIndex].Cells[e.ColumnIndex 0 ? e.ColumnIndex : 0]; dgv.Rows[e.RowIndex].Selected true; _currentRightClickNode _flatList[e.RowIndex]; } }这个细节很容易被忽略不设置的话用户点击右键菜单里的“增加子节点”时CurrentRow 可能不是你预期的那个节点。5. 性能调优与大数据量的现实考验树形表格在数据量小的时候一切都很美好一旦数据量上来性能问题立刻暴露。我遇到过 5000 行数据展开后 UI 卡顿的情况简单优化三步之后流畅度恢复到可接受的程度。5.1 避免全量重建行的替代方案前面说展开折叠时全量重建 Rows这个方案在数据量小于 1000 行时完全没问题。但超过 2000 行时每次点击展开都重建一次会有肉眼可见的卡顿。优化思路是增量更新展开节点时只插入它的子节点行折叠时只移除子节点行。核心逻辑是维护一个节点到行号的映射字典private DictionaryTreeNodeItem, int _rowMap new DictionaryTreeNodeItem, int(); private void ExpandNode(TreeNodeItem node) { int insertAt _rowMap[node] 1; foreach (var child in node.Children) { var newRow BuildRow(child); dgv.Rows.Insert(insertAt, newRow); _rowMap[child] insertAt; insertAt; if (child.IsExpanded child.Children.Count 0) { // 递归插入孙节点同时更新所有后续节点的行号 } } UpdateRowMapAfterIndex(insertAt - 1); }注意每插入一行后该行之后所有节点的行号都会 1所以必须维护并更新 _rowMap。这块逻辑绕但不算复杂核心就是“插入子节点 平移后续行号”。5.2 BeginUpdate / EndUpdate 与 SuspendLayout无论用哪种刷新方式都应当把行更新放在 BeginUpdate 和 EndUpdate 之间配合 SuspendLayout 和 ResumeLayout 使用dgv.SuspendLayout(); dgv.BeginUpdate(); // 增删改行的操作 dgv.EndUpdate(); dgv.ResumeLayout();这会告诉 DataGridView 在操作结束前不要刷新 UI大幅减少绘制次数。实测 2000 行数据展开时这个组合能减少约 40% 的时间消耗。5.3 虚拟模式终极性能方案如果数据量有几万甚至几十万行动态插入也行不通了此时应该用 VirtualMode。启用虚拟模式后DataGridView 不会自己维护 Rows 集合而是通过 CellValueNeeded 事件按需请求单元格内容。虚拟模式的劣势是代码复杂度大幅提升排序、编辑、选中状态全都要自己管理。我的建议是1 万行以内用“增量插入”方案1 万行以上直接上第三方控件或者 VirtualMode中间层没有太多捷径可走。6. Delphi 环境下的 TDBGrid 模拟树思路虽然标题写的是 DataGridView但网上搜这个需求的人有不少其实是用 Delphi 做桌面管理系统的老开发。Delphi 的 TDBGrid 本身不支持树形展示但我用 TTreeView 与 TDBGrid 组合的方式做过几个项目效果还算过得去。6.1 用 TTreeView 联动 TDBGrid左侧放 TTreeView右侧放 TDBGrid。TTreeView 的 OnChange 事件里根据当前选中节点的 Key 去查询数据库再把结果集赋给 TDBGrid.DataSourceprocedure TForm1.tvTreeChange(Sender: TObject; Node: TTreeNode); begin qryData.Close; qryData.SQL.Text : SELECT * FROM employees WHERE dept_id :deptId; qryData.ParamByName(deptId).AsString : Node.Text; // 实际应用用 Node.Data 存 ID qryData.Open; end;这个方案的优点是实现成本低、稳定、不用重写 TDBGrid 的绘制逻辑缺点是表格本身不显示层级层级信息完全靠左边的树来表达。6.2 TDBGrid 内直接绘制缩进与展开按钮如果一定要在 TDBGrid 内部显示层级可以用 OnDrawColumnCell 事件配合 Canvas 手动绘制。TDBGrid 的默认绘制是 OnDefaultDraw你可以在画完默认内容后在树列上叠加绘制缩进矩形和展开三角。这个方法在 Delphi 老版本比如 Delphi 7上性能不错但有一个明显的短板选中状态的背景颜色通常会被默认绘制覆盖你需要自己处理焦点矩形。处理方式是在绘制完成后调用 DrawFocusRect 重新画选中框工作量不算小。6.3 商业控件与开源方案选型建议Delphi 下真要做得完美我建议直接考虑商业控件DevExpress 的 cxTreeList 是 Delphi 生态里树形表格最成熟的方案支持列排序、单元格编辑、节点图标、复选框等特性或者用开源的 Virtual Treeview它本来不是表格控件但通过设置列模式可以模拟表格效果而且性能极强。选择第三方控件的核心标准是看项目预算和维护成本。如果是一次性维护的老系统用 TTreeView TDBGrid 联动就够了如果是长期演进的新项目一次性投资商业控件反而省心。6.4 几种模拟方案的选型对比方案开发成本层级感性能适用场景TreeView DBGrid 联动低中表格无层级高一次性维护快速开发DBGrid 自绘缩进中高中高中表格必须显示层级cxTreeList 商业控件低购买后高高新项目预算充足Virtual Treeview 开源高高极高追求性能愿意花时间从我的经验看如果客户的诉求是“打印报表或导出 Excel 时能看到层级”TreeView 联动方案就不合格因为打印只能打 DBGrid 的内容层级根本显示不出来。这种情况下必须上自绘方案。7. 还原完整踩坑记录缩进错位与滚动条引发的噩梦最后分享一个我实际调试过程中的完整排查链路这个 bug 花了我将近半天时间原因非常简单但非常隐蔽。现象是表格有水平滚动条把滚动条向右拖动一定距离后点击树列的展开按钮明明鼠标点在三角形上但节点却没有展开。但把滚动条拉回最左侧点击同一个位置却又生效了。第一次排查时我怀疑是命中区域计算问题于是一行行加断点检查坐标。发现当水平滚动条不在 0 位置时GetCellDisplayRectangle 返回的 X 坐标包含了滚动偏移而 CellMouseClick 的 e.Location 是相对当前单元格的两者直接比较时滚动偏移被重复计算了一次。更隐蔽的是因为绘制用的是 e.CellBounds 的绝对坐标画文字和按钮而点击时用 GetCellDisplayRectangle 的 X 减去自身的 X 算相对按钮位置这两套坐标混用导致问题只在特定滚动位置出现。最终修复方法就是统一坐标基准绘制时用 e.CellBounds 的绝对值能够正确显示点击时把 e.Location 转换成 DataGridView 绝对坐标也就是加上单元格显示矩形的 X 和 Y再参与命中判断Point absolutePoint new Point( e.Location.X cellRect.X, e.Location.Y cellRect.Y );用绝对坐标统一两处逻辑之后问题彻底消失。这个教训告诉我自绘表格类控件时坐标基准一定要在项目初期就统一要么全部用绝对坐标要么全部用单元格相对坐标千万不能混着来。另一个容易踩的坑是 RowHeadersWidth 也会影响水平偏移。如果你启用了行头RowHeaders那么 GetCellDisplayRectangle 返回的 X 坐标和用户肉眼看到的单元格位置之间还会差一个 RowHeadersWidth 的距离这在处理边缘点击时经常造成几像素的偏差视觉上表现为“按钮画出来和点上去差几个像素”。我最终的代码里统一用 ClientRectangle 和 CurrentCellAddress 做基准换算彻底规避了这个问题。8. 画蛇添足还是锦上添花图片列、复选框与排序功能的取舍树形表格实现之后总会有人想加各种附加功能。这里我想泼几盆冷水帮你判断哪些功能值得做哪些功能纯属给自己挖坑。8.1 排序功能与层级结构的天然冲突DataGridView 默认支持点击列头排序但对树形表格来说一旦排序父子关系的视觉呈现和实际数据顺序可能完全错乱。比如按成员数降序排列父节点和子节点会分散在表格各处层级结构瞬间变成一团乱麻。我的建议是树列必须禁用排序其他列如果要排序只对“当前展开状态下的平铺数据”排序但排序后要重新计算父子缩进否则会出现子节点缩进比父节点还大的错乱情况。或者干脆在树形模式下禁用所有排序另加一个“展开全部后导出到 Excel”的功能让用户自己去 Excel 里排序。8.2 复选框与三态选中业务系统里常见的需求是树形表格加复选框且要支持“父节点选中则子节点全部选中”的三态效果。这个功能实现难度不小因为第三态半选需要额外的状态位而且 DataGridView 的 CheckBox 列没有原生的三态支持。实现方案是用一个 TextBox 列代替 CheckBox 列在 CellPainting 里自己画复选框图标在 CellMouseClick 里处理点击状态然后递归更新所有子节点的状态。这套逻辑完整实现大概需要 200 行代码测试用例也得准备齐全不然很容易出现父节点半选状态和子节点实际状态不一致的问题。8.3 图片列的加载与内存管理给树形表格加图标列看起来是个小需求实际坑也不少。如果从数据库 Blob 字段直接加载图片100 行数据就要创建 100 个 Image 对象内存占用不小而且 DataGridView 释放时如果没正确处理很容易内存泄漏。更稳妥的做法是预先加载一次图标资源绘制单元格时用 DrawImage 绘制到固定的图标区而不是每行绑定一个 Image 对象。图标需要根据行状态选中、失效、叶子节点动态变化时用一个字典缓存图片private Dictionarystring, Image _iconCache new Dictionarystring, Image();图片资源的释放也别忘了在 DataGridView 的 Dispose 或者 FormClosing 时统一处理别让这些图片对象一直挂在内存里。9. 我的一线使用建议从需求出发决定方案写了这么多最后想从项目实战角度做个总结性的建议不是纸上谈兵而是根据我做过的几个真实项目经验来谈。如果你只是给内部管理工具加个部门树展示功能数据量几百行用户也不经常操作层级直接用 TTreeView DataGridView 联动方案最省事半天搞定。如果是做进销存系统的商品分类、BOM 物料清单、组织架构管理这种重层级交互功能的界面一定要用自绘方案而且要在设计阶段就把坐标基准、行状态维护方案确定下来不然后面加功能每改一次都会出一堆低级 bug。如果是新项目、预算允许建议认真评估商业树形表格控件。很多人觉得自绘方案更灵活但灵活的另一面是要自己维护所有边界情况。商业控件帮你省下的时间往往比授权费更值钱。尤其是当需求还包括单元格合并、拖拽排序、跨层级统计、递归编辑这类高级交互时自绘方案的成本会成倍上涨这时候商业控件的性价比就非常明显了。最后再分享一个小技巧无论采用哪种方案测试时一定要包含这三种数据形态只有一层结构的根节点、嵌套超过五层的深节点、单个根节点下面挂上千个子节点的宽节点。这三种数据分别能暴露树形表格在“基本显示”“缩进绘制”“性能表现”三个维度上的问题。我踩过的最深的一次坑就是内部测试数据全是两层结构结果一上线遇到五层嵌套数据缩进把整列宽度占了大半。提前准备好极端数据跑一遍能省下不少上线的麻烦。本文还有配套的精品资源点击获取
返回列表