Terminal.Gui TreeView 深度解析:构建可交互的层次结构树控件

发布时间:2026/9/24 0:36:50
Terminal.Gui TreeView 深度解析:构建可交互的层次结构树控件 UI组件跨平台桌面应用【免费下载链接】Terminal.GuiCross Platform Terminal UI toolkit for .NET项目地址https://gitcode.com/gh_mirrors/te/Terminal.Gui点击查看免费下载Terminal.Gui 是面向 .NET 的跨平台终端 UI 工具包其内置的TreeView控件专门用于展示与导航具有可展开/折叠分支的层次化数据是文件系统浏览器、对象资源管理器、目录导航等 TUI 应用的核心构件。本文基于仓库中的 TreeView 深度指南 并结合 TreeView 源码 与 UICatalog 示例场景系统讲解 TreeView 的两种形态、数据模型、命令体系、外观定制、过滤、复选框模式与动态更新机制读完你即可在终端界面中构建出功能完整的层次树。两种形态TreeView 与 TreeViewTTreeView 以两种形式提供二者共享相同的渲染、导航、选择与命令行为TreeView非泛型便捷类要求所有节点实现ITreeNode接口适合快速原型与简单场景TreeViewT泛型通过ITreeBuilderT展示任意T : class类型对象适合已有领域模型如Army、Unit、文件系统对象的既有数据。从源码看非泛型TreeView实际上是泛型类的特化public class TreeView : TreeViewITreeNode, IDesignable见 TreeView.cs其构造函数自动装配了TreeNodeBuilder作为TreeBuilder并将AspectGetter默认指向o o.Text——这正是非泛型形态零配置的底层原因。使用 TreeNode 快速上手最简用法是直接用TreeNode对象构建层级TreeView tree new () { Width 40, Height 20 }; TreeNode root1 new () { Text Root1 }; root1.Children.Add (new TreeNode { Text Child1.1 }); root1.Children.Add (new TreeNode { Text Child1.2 }); TreeNode root2 new () { Text Root2 }; root2.Children.Add (new TreeNode { Text Child2.1 }); root2.Children.Add (new TreeNode { Text Child2.2 }); tree.AddObject (root1); tree.AddObject (root2);渲染效果├-Root1 │ ├─Child1.1 │ └─Child1.2 └-Root2 ├─Child2.1 └─Child2.2使用 ITreeBuilder 适配既有模型当数据模型已存在时例如Army与Unit类使用泛型TreeViewT配合ITreeBuilderT告诉树对象之间的父子关系TreeViewGameObject tree new () { Width 40, Height 20, TreeBuilder new DelegateTreeBuilderGameObject ( childGetter: o o is Army a ? a.Units : Enumerable.EmptyGameObject (), canExpand: o o is Army) }; tree.AddObject (new Army { Designation 3rd Infantry, Units [new Unit { Name Orc }, new Unit { Name Troll }] });TreeViewT的构造函数见 TreeViewT.cs在初始化时即注册了全部内建命令PageUp/PageDown/Expand/Collapse/Up/Down/SelectAll等并默认启用ViewportSettingsFlags.HasScrollBars滚动条以及将双击鼠标绑定到Command.Accept。数据模型ITreeNode 与 TreeNodeITreeNode是非泛型TreeView的节点接口见 ITreeNode.cs包含三个成员成员类型说明Textstring显示文本ChildrenIListITreeNode子节点列表Tagobject?用户自定义数据TreeNode见 TreeNode.cs是默认的具体实现Text默认值为Unnamed Node。Children是可变的——任何时刻都可以增删节点随后调用RefreshObject更新显示。ITreeBuilderT为TreeViewT实现ITreeBuilderT来描述层次结构见 ITreeBuilder.csclass GameObjectTreeBuilder : ITreeBuilderGameObject { public bool SupportsCanExpand true; public bool CanExpand (GameObject model) model is Army; public IEnumerableGameObject GetChildren (GameObject model) { if (model is Army a) { return a.Units; } return Enumerable.EmptyGameObject (); } }接口包含三个成员SupportsCanExpand是否支持快速的可展开预判断。当true时TreeView 无需拉取子节点即可渲染展开/折叠符号适用于CanExpand代价极低例如按类型判断目录一定可展开的场景CanExpand (T toExpand)判断某模型是否有子节点只有当SupportsCanExpand为true时才会被调用避免对昂贵的GetChildren做重复枚举GetChildren (T forObject)返回指定对象的所有子节点用于按需构建分支。DelegateTreeBuilderTDelegateTreeBuilderT用 lambda 提供同样的能力见 DelegateTreeBuilder.cstree.TreeBuilder new DelegateTreeBuilderGameObject ( childGetter: o o is Army a ? a.Units : Enumerable.EmptyGameObject (), canExpand: o o is Army);构造器内部将_childGetter与_canExpand两个委托分别映射到GetChildren与CanExpand且以base (true)声明SupportsCanExpand true。两个委托均为必填参数——第一个返回子节点第二个执行CanExpand检查。自定义 ITreeNode 子类可以继承TreeNode来包装自己的数据将领域对象直接融入节点class House : TreeNode { public string Address { get; set; } ; public ListRoom Rooms { get; set; } []; public override IListITreeNode Children Rooms.CastITreeNode ().ToList (); public override string Text { get Address; set Address value; } } class Room : TreeNode { public string Name { get; set; } ; public override string Text { get Name; set Name value; } }随后直接添加自定义对象tree.AddObject (new House { Address 23 Nowhere Street, Rooms [new Room { Name Ballroom }, new Room { Name Bedroom }] });这种写法绕过了TreeBuilder完全依赖ITreeNode.Children的覆写来动态计算子节点。命令与输入TreeView 深度集成 Terminal.Gui 的命令系统。输入流经IInputProcessor→KeyBindings/MouseBindings→Command→ 处理器。所有命令在TreeViewT构造函数中通过AddCommand注册且默认按键映射可分层配置TreeView 专属层 基类View.DefaultKeyBindings层。键盘绑定按键命令行为EnterCommand.Accept触发Accepting/AcceptedCWP 模式SpaceCommand.Activate触发Activating/Activated切换展开/折叠CheckboxMode启用时切换复选框→Command.Expand展开选中节点Ctrl→Command.ExpandAll展开节点及其全部后代←Command.Collapse折叠选中节点若已折叠则导航到父节点Ctrl←Command.CollapseAll折叠节点及其全部后代↑Command.Up向上移动一行选择↓Command.Down向下移动一行选择Shift↑Command.UpExtend向上扩展选择多选Shift↓Command.DownExtend向下扩展选择多选Ctrl↑Command.LineUpToFirstBranch跳到同一层级第一个兄弟节点Ctrl↓Command.LineDownToLastBranch跳到同一层级最后一个兄弟节点PageUpCommand.PageUp上翻一页PageDownCommand.PageDown下翻一页ShiftPageUpCommand.PageUpExtend向上扩展一页选择ShiftPageDownCommand.PageDownExtend向下扩展一页选择HomeCommand.Start选中第一个节点EndCommand.End选中最后一个节点CtrlACommand.SelectAll全选需启用MultiSelect任意字母集合导航器跳转到下一个匹配节点上述 TreeView 专属绑定定义在静态属性DefaultKeyBindings中见 TreeViewT.cs包括CursorRight→Expand、CtrlRight→ExpandAll、CtrlUp/CtrlDown→分支首/尾跳转等。该静态属性为进程级配置改动需谨慎不建议在并行化单元测试中修改如需通过配置文件覆盖应使用View.ViewKeyBindings。鼠标行为输入行为单击选中被点击节点。若点击落在展开/折叠符号/-上则切换展开状态CheckboxMode启用且点击落在复选框字形上则切换勾选状态双击触发Command.Accept→ 触发Accepting/AcceptedCWP同时切换展开/折叠滚轮上/下垂直滚动视口滚轮左/右水平滚动视口命令架构TreeView 为Command.Activate与Command.Accept注册了处理器对应OnActivated/OnAccepted覆写Command.ActivateOnActivated切换选中节点的展开/折叠。对于鼠标点击仅当点击展开符号时才切换。这是Space 键与单击的处理器。从 TreeViewT.cs 的实现可见鼠标激活时会先通过HitTest命中测试找到被点击分支命中复选框字形则ToggleChecked命中展开符号则Toggle键盘激活则对SelectedObject操作——CheckboxMode开启时切换勾选否则切换展开。Command.AcceptOnAccepted遵循标准可取消工作流模式CWP——先触发Accepting若未被取消再触发Accepted。鼠标双击时还会切换展开/折叠而Enter 键只触发 Accept不切换展开状态。Command.Toggle无论上下文如何直接切换选中节点的展开/折叠。基类View将 Space 绑定到Command.Activate但 TreeView 也显式支持Command.Toggle内部调用Space ()方法。此外UICatalog 的文件浏览器场景TreeViewFileSystem.cs展示了如何利用Activating事件实现右键上下文菜单仅处理MouseBinding类型的激活通过GetObjectOnRow定位被右键的对象再注册并弹出PopoverMenu。事件Accepting 与 AcceptedCWPTreeView 对Accept遵循标准的可取消工作流模式tree.Accepting (sender, e) { // 在 Enter 键或双击时触发 // 设置 e.Cancel true 可阻止 Accepted 触发 }; tree.Accepted (_, _) { // 节点被接受确认 ITreeNode? selected tree.SelectedObject; };触发源触发事件Enter 键Accepting→Accepted双击Accepting→AcceptedSpace 键Activating→Activated而非 Accept单击展开符号Activating→Activated而非 AcceptSelectionChanged每当SelectedObject变化时触发事件参数定义见 SelectionChangedEventArgs.cs包含Tree、OldValue、NewValuetree.SelectionChanged (sender, e) { // e.OldValue 是之前选中的对象 // e.NewValue 是当前选中的对象 };在文件浏览器场景中该事件被用于联动右侧详情面板TreeViewFiles_SelectionChanged直接调用ShowPropertiesOf (e.NewValue)展示文件/文件夹的路径、大小与修改时间。DrawLine渲染每一行可见内容时触发允许逐行定制事件参数见 DrawTreeViewLineEventArgs.cstree.DrawLine (sender, e) { // e.Model 是正在绘制的对象 // e.IndexOfModelText 是文本起始列 // e.Cells 是要渲染的单元格列表 // 设置 e.Handled true 可抑制默认渲染 };事件参数还提供IndexOfExpandCollapseSymbol展开/折叠符号在Cells中的索引、Y视口内行号、Tree等成员。注意Cells的长度变化可能导致渲染损坏。文件浏览器场景中正是用DrawLine将目录图标染成亮黄色private void TreeViewFiles_DrawLine (object? sender, DrawTreeViewLineEventArgsIFileSystemInfo e) { if (e.Model is not IDirectoryInfo) { return; } // ... 通过 e.IndexOfModelText 定位文本单元格并修改其 Attribute }外观定制TreeStyle通过TreeStyle属性控制渲染定义见 TreeStyle.cs属性默认值说明ShowBranchLinestrue显示│、├、└连接线ExpandableSymbol可展开折叠态节点的符号CollapseableSymbol-已展开节点的符号ColorExpandSymbolfalse用高亮色渲染展开符号对应VisualRole.HighlightInvertExpandSymbolColorsfalse交换展开符号的前景色/背景色HighlightModelTextOnlyfalse仅高亮文本而非整行将符号设为null可完全隐藏。ShowBranchLines设为false时仅用空白占位。文件浏览器场景用菜单复选框动态切换这些选项/-、/v、无符号等对应Style.ExpandableSymbol/Style.CollapseableSymbol的运行时赋值。AspectGetter默认情况下TreeView 用每个节点的ToString()渲染文本。可通过AspectGetter覆写其委托类型见 AspectGetterDelegate.cstreeViewFiles.AspectGetter f f.FullName;文件浏览器场景的SetFullName展示了在 显示文件名 与 显示完整路径 之间动态切换的做法。源码默认值为o o.ToString () ?? 见 TreeViewT.cs非泛型TreeView则覆写为o o.Text。ColorGetter为单个节点指定配色方案tree.ColorGetter node { if (node is HiddenFile) { return new Scheme { Normal new Attribute (Color.Gray, Color.Black) }; } return null; // 使用默认配色 };ColorGetter返回Scheme?为null时使用默认方案。从 TreeViewT.cs 的注释可知TreeView 只使用返回 Scheme 中的Normal未选中分支、Focus树获得焦点时的选中分支、Active树未获焦点时的选中分支三种角色需要更精细的渲染控制时请改用DrawLine事件。文件浏览器场景对隐藏文件返回BrightYellow/BrightRed前景色。导航与选择编程式导航方法说明GoTo (T obj)选中并滚动到指定对象GoToFirst ()选中第一个根节点GoToEnd ()选中最后一个可见节点EnsureVisible (T obj)滚动使对象进入视口Expand (T obj)展开指定节点ExpandAll (T obj)展开节点及其全部后代Collapse (T obj)折叠指定节点CollapseAll (T obj)折叠节点及其全部后代Toggle (T obj)切换展开/折叠IsExpanded (T obj)检查节点是否已展开GetParent (T obj)获取父节点仅对已展开分支有效GetChildren (T obj)获取可见子节点GetObjectOnRow (int row)获取视口某行的对象这些方法定义于 TreeView.Navigation.cs。其中EnsureVisible通过比较节点索引与ScrollOffsetVertical决定滚动方向GetParent/GetChildren依赖BuildLineMap ()生成的当前可见分支映射因此父节点折叠后返回的索引为 -1 或空集合。GetScrollOffsetOf可获取对象在树中的行索引配合ScrollOffsetVertical实现精确滚动。多选通过MultiSelect属性启用默认truetree.MultiSelect true;按键行为Shift↑/↓按一行扩展选择ShiftPageUp/PageDown按一页扩展选择CtrlA全选可见节点使用GetAllSelectedObjects ()获取全部选中对象。实现层面多选区域维护在_multiSelectedRegions栈中TreeSelectionTAdjustSelection会依据expandSelection与MultiSelect决定清除或扩展区域普通单步导航会清除扩展选择。SelectAll在未启用MultiSelect时直接返回。字母导航当AllowLetterBasedNavigation为true默认时按下字母键会跳到下一个AspectGetter文本匹配的节点。该功能依赖KeystrokeNavigator属性支持连续快速输入多个字符来精化匹配TreeViewCollectionNavigatorMatcherT负责判断按键兼容性见 TreeViewCollectionNavigatorMatcher.cs。与自定义按键绑定冲突时可关闭tree.AllowLetterBasedNavigation false;从OnKeyDown覆写见 TreeView.Navigation.cs可见其处理顺序先检查是否命中已绑定的按键命令命中则交还常规 KeyDown 流程允许覆写默认处理再尝试字母导航。过滤应用过滤器仅显示匹配节点以及通向它们的祖先路径tree.Filter new TreeViewTextFilterFileSystemInfo (tree) { Text *.cs };实现ITreeViewFilterT自定义逻辑接口定义见 ITreeViewFilter.csclass MyFilter : ITreeViewFilterGameObject { public bool IsMatch (GameObject model) model.ToString ().Contains (Orc); }过滤器激活时通向匹配项的父节点即使自身不匹配也保持可见确保树结构可导航。Filter置为null即移除过滤。内置的TreeViewTextFilterT见 TreeViewTextFilter.cs默认使用StringComparison.OrdinalIgnoreCase大小写不敏感匹配Text属性赋值会立即调用InvalidateLineMap ()与SetNeedsDraw ()刷新树。过滤的底层逻辑在BuildLineMap/AddToLineMap见 TreeViewT.cs递归遍历时只要当前分支匹配、或父分支匹配、或存在匹配的后代该分支就会保留在行映射中这正是祖先路径得以保留的实现依据。复选框模式设置CheckboxMode true启用内建复选框。每个节点在展开符号与文本之间显示复选框字形tree.CheckboxMode true;渲染效果├-☐ Root1 │ ├─☐ Child1.1 │ └─☐ Child1.2 └-☐ Root2三态语义TreeView 实现标准三态复选框语义父节点状态含义未勾选无后代被勾选已勾选全部后代被勾选不确定Indeterminate部分非全部后代被勾选切换父节点会将新状态传播到所有后代切换叶子节点会通过推导自动更新祖先状态不确定状态始终是推导结果——用户无法直接设置。源码中的推导逻辑见GetEffectiveCheckStateTreeView.Navigation.cs显式状态保存在_checkedStates字典父节点有效状态依据其已知子分支的状态聚合而来——全部勾选为Checked、全部未勾选为UnChecked、混合为None三态。SetCheckedRecursive负责向全部后代传播。与复选框交互输入行为Space切换选中节点的勾选状态点击复选框字形切换被点击节点的勾选状态对应实现分别在Space ()方法与OnActivated的鼠标命中分支中见 TreeView.Navigation.cs 与 TreeViewT.cs。编程式访问// 获取有效勾选状态父节点为推导值 CheckState state tree.GetCheckState (node); // 设置勾选状态传播到后代 tree.SetChecked (node, CheckState.Checked); // 获取全部已勾选对象含由子节点推导为已勾选的父节点 IEnumerableT checkedObjects tree.GetCheckedObjects ();GetCheckedObjects ()按树顺序返回勾选对象并附带当前不在树中的已勾选对象基于_checkedStates字典兜底。CheckedChanged 事件每个状态发生变化的节点都会触发传播过程中的后代同样触发tree.CheckedChanged (sender, e) { // e.Object — 状态变化的节点 // e.OldValue — 之前的 CheckState // e.NewValue — 新的 CheckState };事件参数类型为CheckedChangedEventArgsT见 CheckedChangedEventArgs.cs。CheckboxMode 与 CheckBoxTableSourceWrapper当使用TreeTableSourceT在TableView中展示树时CheckboxMode的复选框字形不会渲染在表格列中。要为树形表格添加复选框需用CheckBoxTableSourceWrapperByIndex或CheckBoxTableSourceWrapperByObjectT包装TreeTableSource详见 TableView — Checkbox 列相关实现位于 CheckBoxTableSourceWrapper.cs。动态更新TreeView 会缓存展开后的树结构_cachedLineMap。运行时修改节点后需按需刷新方法使用时机RefreshObject (T obj)修改节点的子节点或文本之后RebuildTree ()替换TreeBuilder或做了大规模改动之后InvalidateLineMap ()影响可见行集合的变化之后运行时添加子节点的示例TreeNode parent (TreeNode)tree.SelectedObject!; parent.Children.Add (new TreeNode { Text New Child }); tree.RefreshObject (parent);移除节点的示例ITreeNode toDelete tree.SelectedObject!; if (tree.Objects.Contains (toDelete)) { // 是根节点 tree.Remove (toDelete); } else { // 是子节点 — 从父节点 Children 中移除 ITreeNode? parent tree.GetParent (toDelete); parent?.Children.Remove (toDelete); tree.RefreshObject (parent); }RefreshObject支持startAtTop参数true时连同祖先一起刷新InvalidateLineMap内部会立即重算内容尺寸UpdateContentSize使 View 内容区系统在展开/折叠后保持同步例如滚动偏移钳制。注意Remove在移除根对象时会同时清理其全部后代的显式勾选状态。实际案例文件系统浏览器仓库的 TreeViewFileSystem.cs 是 TreeView 各特性的综合演示可运行于 UICatalog 场景列表中用FileSystemTreeBuilder作为TreeBuilder以驱动器根目录为根对象AddObjects用AspectGetter组合文件系统图标与名称用Style系列开关连接线、/-、/v、无符号、彩色/反色符号、仅高亮文本动态调整外观用ColorGetter为隐藏文件着色用DrawLine把目录图标染成亮黄色用SelectionChanged联动详情面板用ActivatingGetObjectOnRow实现右键上下文菜单用IsExpanded驱动图标开合状态。此外 TreeUseCases.cs、TreeUseCases.MiddleEarthArmy.cs、InteractiveTree.cs 等场景提供了更多数据模型与交互的参考用例。关联文档Command Deep Dive — 命令架构与输入路由Cancellable Workflow Pattern —Accepting/Accepted事件模型Views Catalog — 全部内建视图概览Keyboard Deep Dive — 按键处理管线TableView — Checkbox 列 — 树形表格中的复选框包装器赞分享UI组件跨平台桌面应用【免费下载链接】Terminal.GuiCross Platform Terminal UI toolkit for .NET项目地址https://gitcode.com/gh_mirrors/te/Terminal.Gui点击查看免费下载相关推荐termscp 未来路线图展望即将推出的新功能和改进termscp 未来路线图展望即将推出的新功能和改进 termscp 是一款功能丰富的终端 UI 文件传输和资源管理器支持 SCP/SFTP/FTP/S3/开发工具优雅构建层次化界面Bootstrap TreeView深度解析与实战指南优雅构建层次化界面Bootstrap TreeView深度解析与实战指南 在现代Web开发中层次化数据展示已成为众多应用场景的核心需求。Bootstrap前端UI库/组件Sidekiq-Cron安全配置防止定时任务被恶意攻击Sidekiq Cron安全配置防止定时任务被恶意攻击 Sidekiq Cron作为Sidekiq的定时任务扩展允许开发者通过类似Cron的语法安排后台任务后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考