拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Terminal.Gui 导航术语表(Navigation Lexicon):焦点、焦点链与 Tab 导航体系完全指南

UI组件跨平台桌面应用【免费下载链接】Terminal.GuiCross Platform Terminal UI toolkit for .NET项目地址https://gitcode.com/gh_mirrors/te/Terminal.Gui点击查看免费下载导读本文以 Terminal.Gui 官方文档中的导航术语表为核心骨架系统梳理该 .NET 跨平台终端 UI 工具包中与焦点与导航相关的基础术语Cursor、Focus、Focus Chain、TabStop、TabGroup 等并结合 Navigation Deep Dive、Application.DefaultKeyBindings与ApplicationNavigation源码深入讲解每个术语在代码层面的真实含义、默认按键映射与可验证的行为细节。读完本文你将能准确理解 Terminal.Gui v2 的焦点模型能区分TabStop与TabGroup的导航差异并能在自己的应用里正确配置键盘导航与焦点行为。术语表总览理解 Terminal.Gui 导航的 9 个核心词汇navigation-lexicon.md定义了 Terminal.Gui 导航体系的 9 个基础术语它们构成了整个焦点与键盘导航模型的语言基础术语含义Cursor向用户指示键盘输入将作用在何处的视觉指示器。每个终端会话有且仅有一个 Cursor。详见 Cursor 深度文档Enter / Gain指某个原本未聚焦的 View 即将成为聚焦状态。视图进入焦点与视图获得焦点是同一含义。这两个词是 v1 时代的遗留术语Focus某个 UI 元素View处于被选中状态、准备好接收用户输入的状态。拥有焦点的元素通常会响应键盘事件与其他交互Focus Chain可接收焦点的 UI 元素的有序序列从当前聚焦的元素开始沿其父SuperView链一路延伸到焦点树的根部Application.Top。整个应用中只有一个焦点链拥有焦点top.HasFocus true且每个焦点链中有且仅有一个View 是最聚焦most-focused的——它才是真正接收键盘输入的那个Focus Ordering可聚焦 View 被遍历导航的顺序。UI 框架中通常用它来支持屏幕阅读器、提升应用可访问性。v1 中通过TabIndex/TabIndexes实现Leave / Lose指某个原本聚焦的 View 即将失去聚焦状态。视图离开焦点与视图失去焦点是同一含义。同样为 v1 遗留术语Navigation用户在应用视图层次结构中移动焦点的用户体验Tab描述所有键盘上都有的Tab键、比空格更宽的文本断点或作为键盘导航停靠点的 UI 元素。该词源自打字机并因所有键盘上都存在Tab键而被强化TabGroup一个作为其他可聚焦视图容器的View。Command.NextTabGroup与Command.PreviousTabGroup的默认按键分别是Key.PageDown.WithCtrl与Key.PageUp.WithCtrl见Application.DefaultKeyBindings。这些按键使用户可以通过键盘在视图层次结构中上下导航TabStop键盘导航的最终停靠点 View。此处的ultimate指该 View 没有可聚焦的子视图。Command.NextTabStop与Command.PreviousTabStop的默认按键分别是Key.Tab与Key.Tab.WithShift。这些按键只在同级视图peer-views之间导航注意术语表中隐含的版本差异术语表中对 TabGroup/TabStop 默认键的描述PageDown.WithCtrl/PageUp.WithCtrl来自文档编写时的状态而当前仓库源码中Application.DefaultKeyBindings的实际映射为Tab/Tab.WithShiftNextTabStop/PreviousTabStop与F6/F6.WithShiftNextTabGroup/PreviousTabGroup。以当前仓库源码为准下文将结合源码详细说明。从术语到源码默认导航键的实际绑定Application.DefaultKeyBindings的真实映射术语表引用了Application.DefaultKeyBindings它定义在 Terminal.Gui/App/Application.cs是当前仓库中导航键绑定的权威来源public static DictionaryCommand, PlatformKeyBinding? DefaultKeyBindings { get; set; } new () { [Command.Quit] Bind.All (Key.Esc), [Command.Suspend] Bind.NonWindows (Key.Z.WithCtrl), [Command.Arrange] Bind.All (Key.F5.WithCtrl), [Command.NextTabStop] Bind.All (Key.Tab), [Command.PreviousTabStop] Bind.All (Key.Tab.WithShift), [Command.NextTabGroup] Bind.All (Key.F6), [Command.PreviousTabGroup] Bind.All (Key.F6.WithShift), [Command.Refresh] Bind.All (Key.F5) };与术语表PageDown.WithCtrl/PageUp.WithCtrl相比当前源码使用Tab/ShiftTab—— 在TabStop视图之间导航F6/ShiftF6—— 在TabGroup容器之间导航。Navigation Deep Dive 中明确说明F6的选择遵循了 Windows 平台的通用键盘加速键约定Windows 应用的常用键盘加速键中 F6 用于在不同区域间移动焦点。这些键绑到了哪里ApplicationKeyboard在 Terminal.Gui/App/Keyboard/ApplicationKeyboard.cs 中将上述命令与Application.Navigation.AdvanceFocus关联起来AddCommand (Command.NextTabStop, () App?.Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop)); AddCommand (Command.PreviousTabStop, () App?.Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop)); AddCommand (Command.NextTabGroup, () App?.Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup)); AddCommand (Command.PreviousTabGroup, () App?.Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup));同时方向键也映射到同级导航命令ApplicationKeyboard.csKeyBindings.ReplaceCommands (Key.CursorRight, Command.NextTabStop); KeyBindings.ReplaceCommands (Key.CursorDown, Command.NextTabStop); KeyBindings.ReplaceCommands (Key.CursorLeft, Command.PreviousTabStop); KeyBindings.ReplaceCommands (Key.CursorUp, Command.PreviousTabStop);这些绑定全部注册为KeyBindingScope.Application作用域——这是优先级最低的绑定作用域因此任何 View 都可以覆盖这些默认按键行为。典型例子是Editor默认覆盖Key.Tab从而允许用户在文本中直接输入制表符\t。关键结论ApplicationKeyboard只负责按键→命令的翻译真正的焦点移动逻辑在ApplicationNavigation.AdvanceFocus()中实现。这两个类共同构成了术语表中 Navigation 一词的运行时实体。Focus 与 Focus Chain应用级焦点模型唯一性铁律One Focus Per App术语表强调整个应用同一时刻只有一个焦点链拥有焦点且每个焦点链中只有一个 View 是最聚焦的接收键盘输入的那个。这与 navigation.md 中列出的第一条导航原则TenetOne Focus Per App 完全一致——框架必须保证不会出现两个 View 同时成为最聚焦视图的情况。在代码层面这个最聚焦视图由ApplicationNavigation维护。查看 Terminal.Gui/App/ApplicationNavigation.csprivate View? _focused; public event EventHandlerEventArgs? FocusedChanged; /// summaryGets the most focused see crefView/ in the application, if there is one./summary public View? GetFocused () _focused;GetFocused()返回应用中最聚焦most-focused的 View如果没有视图拥有焦点返回null极为罕见。它取代了 v1 的View.MostFocused/Application.TopRunnable.MostFocused模式。FocusedChanged/FocusedChanging事件在最聚焦视图已改变 / 即将改变时触发。前者适合做全局响应例如AdornmentsEditor根据当前焦点视图更新编辑器后者适合在整个应用层面拦截/否决焦点变更。SetFocused()是内部方法它记录焦点变化、强制光标刷新并触发FocusedChanged。Focus Chain 的判断规则术语表中的 Focus Chain 定义可翻译为如下可验证的代码事实HasFocus属性若v.HasFocus true则v的整个 SuperView 链上所有视图必须都是可聚焦的链上所有祖先视图的HasFocus也均为truev的更深层可聚焦子视图中最深的那一个同样HasFocus true。因此v.HasFocus true并不必然意味着v是最聚焦视图——如果它有可聚焦子视图那么真正接收输入的是链中最深的那一个可由Application.Navigation.GetFocused()查询。以Window - Dialog - Button三层结构为例window.HasFocus true; // 位于焦点链中 dialog.HasFocus true; // 位于焦点链中 button.HasFocus true; // 实际最聚焦的视图 var mostFocused Application.Navigation.GetFocused (); // 返回 buttonView.HasFocus的底层字段是私有布尔值_hasFocus它是判断视图是否拥有焦点的最终事实来源见 View.Navigation.cs 中HasFocus属性实现。焦点链的视觉反馈用户如何看出焦点链答案是ColorScheme.Focus属性处于焦点链中的视图使用其ColorScheme.Focus样式渲染最聚焦的视图焦点链最深处可能通过View.Cursor显示终端光标同一时刻只显示一个终端光标由ApplicationNavigation统一管理。自定义焦点样式可在绘制阶段依据HasFocus分支处理protected override void OnDrawContent (Rectangle viewport) { var attribute HasFocus ? GetFocusColor () : GetNormalColor (); Driver.SetAttribute (attribute); // ... 绘制内容 }TabStop 与 TabGroup键盘导航的两级停靠点一个 View 什么时候可聚焦要理解 TabStop/TabGroup先要明确可聚焦的完整判定链。一个 View 要获得焦点必须同时满足仅键盘导航需要第 4 条VisibletrueEnabledtrueCanFocustrueTabStop!TabBehavior.NoStop仅对键盘导航而言。前三条对鼠标导航同样有效一个Visible Enabled CanFocus true的视图可以被鼠标点击聚焦也可以被代码显式SetFocus()。TabStop只影响键盘导航对鼠标导航毫无影响。TabBehavior枚举的四个取值TabBehavior定义于 Terminal.Gui/ViewBase/Navigation/TabBehavior.cs结合 navigation.md 的说明各取值语义如下取值语义对键盘导航的影响null未初始化视图仍在初始化中作为set_CanFocus的触发信号自动把TabStop置为TabStop最常见的便捷用例。判断键盘可聚焦性时等价于NoStop视同不可用TabBehavior.NoStop阻止用户通过键盘导航让该视图及其子视图获得焦点跳过仍可被鼠标或代码聚焦TabBehavior.TabStop可聚焦视图且没有可聚焦子视图。NextTabStop/PreviousTabStop只在同级视图SuperView.SubViews之间推进作为同级间导航的最终停靠点TabBehavior.TabGroup可聚焦视图同时是其他可聚焦视图的容器支持跨容器键盘导航平铺与重叠布局均适用。NextTabGroup/PreviousTabGroup在整个应用范围内跨越所有TabGroup视图推进除非被某个NoStopSuperView 阻断作为组间导航的停靠点源码枚举值NoStop 0、TabStop 1、TabGroup 2。典型容器的默认配置从源码看框架内建容器的TabStop配置见 navigation.md 的 TabBehavior 一节FrameView—— 平铺场景的可见容器TabStop TabBehavior.TabGroupArrangement ViewArrangement.FixedWindow—— 重叠场景的可见容器TabStop TabBehavior.TabGroupArrangement ViewArrangement.Movable | ViewArrangement.Resizable | ViewArrangement.Overlapped。这意味着典型的终端 UI 是外层 TabGroup容器 内层 TabStop控件的两级结构Tab/ShiftTab 在同级控件之间移动F6/ShiftF6 在不同容器之间跳转。何时使用 NoStopNoStop的典型用途是看得见、能被鼠标点中、但不能被 Tab 键盘导航打扰的视图。注意它是递归阻断的一个NoStop视图的子视图也无法通过键盘导航获得焦点但鼠标/代码仍可聚焦。这与 v1 中CanFocus与TabStop紧密耦合、充满魔法逻辑的做法形成对比——v2 的目标是解耦这些概念CanFocus true的视图可以同时TabStop NoStop且仍可被鼠标聚焦。AdvanceFocus导航的实际执行者应用级ApplicationNavigation.AdvanceFocusApplication.Navigation.AdvanceFocus (direction, behavior)把导航请求转发到当前顶层视图public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior) { if (App?.Popovers?.GetActivePopover () is { Visible: true } visiblePopover) { return visiblePopover.AdvanceFocus (direction, behavior); } return App?.TopRunnableView is { } App.TopRunnableView.AdvanceFocus (direction, behavior); }注意两个细节ApplicationNavigation.csPopover 优先如果有可见的活动 Popover焦点在其内部推进——这保证弹出层不会被底层视图的导航抢占该方法由app.Init()期间创建的应用级按键绑定所调用同时作为public便捷方法对外开放。视图级View.AdvanceFocus真正复杂的遍历逻辑在View.AdvanceFocusView.Navigation.cs 起。从实现骨架可以推断其核心流程先尝试在当前视图内推进若存在聚焦的子视图递归调用其AdvanceFocus若未推进成功则尝试换行wrap或上移到 SuperView即AdvanceFocusChain()——在焦点链层面寻找下一个符合behavior过滤条件的视图找不到下一个时通常会回绕到同级第一个符合条件的视图容器TabGroup则借助PreviouslyFocused记录恢复上次聚焦的子视图。方法签名public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)返回true表示焦点已改变或保持在原视图false表示未能推进。导航方向与行为过滤参数取值说明directionNavigationDirection.Forward/Backward前进 / 后退遍历方向behaviorTabBehavior?TabStop/TabGroup/NoStop/null作为过滤器只命中符合该行为的视图例如AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop)表示在同级 TabStop 视图中向前推进——这正是Tab键的语义AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup)正是ShiftF6的语义。鼠标导航与 RestoreFocus鼠标导航遵循之前是否聚焦过的规则见 navigation.md 的 Mouse Navigation 一节若容器之前聚焦过系统记录其子视图中上次最聚焦的那个点击容器时调用RestoreFocus()恢复该子视图焦点若容器之前未聚焦调用AdvanceFocus()寻找下一个合适的聚焦目标。因此框架必须包含清除RestoreFocus()焦点缓存的逻辑当某个原本可聚焦的视图因Visible等变化而变得不可聚焦时缓存必须失效否则会尝试恢复到一个不可聚焦的视图上。编程接口速查代码控制焦点让视图获得焦点SetFocus()是开发者让视图获得焦点的首要公开方法v2 中它可能返回false视图不可聚焦或变更被取消时if (myButton.SetFocus ()) { Console.WriteLine (Button now has focus); } else { Console.WriteLine (Could not focus button); } // 等价写法直接设置 HasFocus 属性效果与 SetFocus() 相同也可能会失败 myButton.HasFocus true;让视图失去焦点让其他视图获得焦点是最典型的失去焦点方式此外视图在失去可聚焦条件时也会自动失去焦点otherView.SetFocus (); // 焦点转移 Application.Navigation.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); myView.CanFocus false; // 若持有焦点则失去 myView.Visible false; // 若持有焦点则失去 myView.Enabled false; // 若持有焦点则失去监听焦点变化视图级通过HasFocusChanging与HasFocusChanged事件以及对应的OnHasFocusChanging/OnHasFocusChanged虚方法感知焦点变化view.HasFocusChanging (sender, e) { if (e.NewValue !ValidateCanFocus ()) { e.Cancel true; // 阻止获得焦点 } }; view.HasFocusChanged (sender, e) { if (e.CurrentValue) { OnViewGainedFocus (); } else { OnViewLostFocus (); } };子类还可以直接覆写OnHasFocusChanging(CancelEventArgsbool e)并在条件满足时e.Cancel true这是灵活覆盖原则Tenet: Flexible Overrides的典型用法。应用级监听与拦截var app Application.Create (); app.Init (); // 监听全局焦点变化 app.Navigation.FocusedChanged (sender, e) { var focused app.Navigation.GetFocused (); StatusBar.Text $Focused: {focused?.GetType ().Name ?? None}; }; // 在应用层面阻止特定视图获得焦点 app.Navigation.FocusedChanging (sender, e) { if (e.NewView is SomeRestrictedView) { e.Cancel true; // 阻止焦点变更 } }; // 编程式导航 Application.Navigation.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); Application.Navigation.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup);v2 与 v1 的关键差异能力v1v2最聚焦视图查询Application.TopRunnable.MostFocusedApplication.Navigation.GetFocused()Add()副作用自动把祖先链CanFocus全部置为true不自动修改任何CanFocusCanFocus与TabStop紧密耦合、自动联动解耦各自独立设置导航方法数量分散在Application/Runnable中约十余个方法统一收敛为Application.Navigation.AdvanceFocusv2 中Add(view)仍会在view.CanFocus true时自动设置其TabStop便捷性保留但不会因为TabStop变化而自动修改CanFocus也不再向上传染CanFocus。开发者需要为期望获得焦点的每个视图显式设置CanFocusvar container new FrameView () { Title Container, CanFocus true, // 必须显式设置 TabStop TabBehavior.TabGroup }; var button new Button () { Text Click Me, CanFocus true, // 必须显式设置 TabStop TabBehavior.TabStop // Add() 会自动设置但可显式覆盖 }; container.Add (button); // 不会自动把 container 的 CanFocus 设为 true常用导航模式实战对话框导航var dialog new Dialog () { Title Settings, CanFocus true, TabStop TabBehavior.TabGroup }; var okButton new Button () { Text OK, IsDefault true }; var cancelButton new Button () { Text Cancel }; // Tab 在按钮之间导航Enter 激活默认按钮 dialog.Add (okButton, cancelButton);双栏容器导航var leftPanel new FrameView () { Title Options, TabStop TabBehavior.TabGroup, X 0, Width Dim.Percent (50) }; var rightPanel new FrameView () { Title Preview, TabStop TabBehavior.TabGroup, X Pos.Right (leftPanel), Width Dim.Fill () }; // F6 在左右面板之间跳转Tab 在面板内部控件之间移动列表导航var listView new ListView () { CanFocus true, TabStop TabBehavior.TabStop }; // 方向键导航条目Enter 选择Space 切换 listView.KeyBindings.Add (Key.CursorUp, Command.Up); listView.KeyBindings.Add (Key.CursorDown, Command.Down); listView.KeyBindings.Add (Key.Enter, Command.Accept);访问性最佳实践Terminal.Gui 导航体系的设计目标之一是可访问性navigation.md 的 Accessibility Considerations 一节键盘可达一切功能都应能通过键盘操作内置 View 均通过单元测试保证至少有一个可推进焦点的导航键见测试 AllViewsNavigationTests.cs 中的AllViews_AtLeastOneNavKey_Leaves它验证所有内置 View 都满足至少一个导航键可推进焦点。焦点指示清晰焦点指示不依赖单一颜色热键以下划线字符视觉标示。提供有意义的标签与逻辑 Tab 顺序// 提供有意义的标签下划线字符即热键 var button new Button () { Text _Save Document, HotKey Key.S }; // 设置逻辑 Tab 顺序 container.TabStop TabBehavior.TabGroup; foreach (var view in container.Subviews) { view.TabStop TabBehavior.TabStop; } // 为鼠标动作提供键盘替代 view.KeyBindings.Add (Key.F10, Command.Context); // 等效右键 view.KeyBindings.Add (Key.Space, Command.Activate); // 等效点击内置视图的输入交互对照以下是 navigation.md 提供的内置 View 输入交互总表节选核心视图帮助理解不同控件在热键、激活、接受、点击聚焦上的差异ViewHotKeysActivate CmdAccept CmdHotKey CmdClick FocusRightClickView1OnSelectOnAcceptFocusFocus-Label1OnSelectOnAcceptFocusNextFocusFocusNextButton1OnSelectFocusOnAcceptFocusOnAcceptHotKeySelectCheckBox1OnSelectAdvanceOnAcceptOnAcceptSelectSelectListView1MarkUnMarkRowOpenSelectedOnAcceptOnAcceptSetMarkOnSelectedChanged-TextField1-OnAcceptFocusFocusContextMenuEditor1-OnAcceptFocusFocusContextMenu表头解读States视图可拥有的视觉/功能状态数量Static是否为纯展示型非交互Default是否可作为默认按钮Enter 激活Activate CmdCommand.Activate触发时的行为Accept CmdCommand.Accept触发时的行为HotKey Cmd按下视图热键时的行为Click Focus点击时若CanFocus true的行为DblClick / RightClick / GrabMouse双击、右键、是否捕获鼠标进行拖拽。总结把术语表翻译成开发实践回到 navigation-lexicon.md 这份术语表它不仅是阅读文档的字典更是理解 Terminal.Gui v2 导航设计哲学的第一块拼图。把 9 个术语串联成一条开发主线你可以记住Focus是视图准备好接收输入的状态Focus Chain是它的传递路径整个应用只有一条焦点链活跃链中只有一个 View 最聚焦Cursor是唯一的视觉聚焦指示器由ApplicationNavigation每帧统一管理ApplicationNavigation.UpdateCursor只跟随最聚焦视图TabStop / TabGroup / Tab共同构成两级键盘导航Tab/ShiftTab在同级 TabStop 间移动F6/ShiftF6在 TabGroup 容器间跳转默认绑定以 Application.cs 的DefaultKeyBindings为准并可通过配置覆盖Enter/Gain与Leave/Lose是 v1 遗留的说法对应 v2 的HasFocusChanging/HasFocusChanged事件体系Navigation / Focus Ordering是这一整套机制的最终目标让用户包括依赖键盘与屏幕阅读器的用户在复杂视图层次中始终有路可走。如需继续深入建议依次阅读导航深度文档、键盘深度文档、光标管理、鼠标深度文档并在 UICatalog 场景集 中通过AllViewsTester等场景实际体验 Tab / F6 导航行为。赞分享UI组件跨平台桌面应用【免费下载链接】Terminal.GuiCross Platform Terminal UI toolkit for .NET项目地址https://gitcode.com/gh_mirrors/te/Terminal.Gui点击查看免费下载相关推荐BrowserSkill human-loop实现详解request-help从发出到用户接管的完整链路BrowserSkill human loop实现详解request help从发出到用户接管的完整链路 BrowserSkill 让 AI Agent 使用人工智能AI 应用AI 技能浏览器控制dsh-plugin告别焦点混乱egui输入导航完全指南告别焦点混乱egui输入导航完全指南 你是否曾在开发界面时为输入框焦点丢失、Tab键导航错乱而头疼作为开发者我们都希望用户能流畅地在输入框、按钮间切换UI组件前端桌面应用BetterScroll无障碍焦点管理键盘导航与焦点陷阱BetterScroll无障碍焦点管理键盘导航与焦点陷阱 在现代Web应用开发中无障碍访问A11Y已成为不可或缺的一部分。然而许多前端滚动库在实现流畅前端UI组件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门