- UI组件
- 跨平台
- 桌面应用
【免费下载链接】Terminal.Gui
Cross Platform Terminal UI toolkit for .NET
本篇指南围绕 Terminal.Gui 的 View 组合(Composition)模型展开,系统讲解一个 View 从外层 Frame 到内层 Content Area 的六层嵌套结构:每层由 Thickness 定义四边宽度、由 Rectangle 定义位置与尺寸,以及 Viewport 如何"看向" Content Area 实现滚动。读完本文,你将掌握 View 各层属性的准确语义、它们之间的几何换算关系,并能基于源码级实现写出正确的布局与滚动代码。
一、View 组合结构总览
Terminal.Gui 中每一个 View 都不是单一矩形,而是由多层矩形嵌套组合而成。官方文档 view-composition.md 用下面的类图给出了权威的静态结构:
从图中可以提炼出三个关键事实:
- View 拥有三种 AdornmentImpl:
Margin(最外层)、Border(边框线与标题)、Padding(最内层,滚动条绘制位置),它们共同把Frame与Viewport分隔开; - AdornmentImpl 拥有 Thickness:每层的厚度由
Top/Right/Bottom/Left四个整数分别定义; - View 拥有 Rectangle:
Frame定义了相对于 SuperView 的位置与尺寸,Viewport则是看向ContentArea的"窗口"。
按从外到内的顺序,View 的组合层次依次为:
- Frame:最外层矩形,定义 View 相对 SuperView 的位置与尺寸;
- Margin:将本 View 与其他 SubView 分隔开(外间距);
- Border:承载视觉边框线与标题(Title);
- Padding:将 Viewport 从 Border 向内偏移(内间距,滚动条所在层);
- Viewport:Content Area 中当前可见的部分;
- Content Area:内容实际绘制区域(可大于 Viewport,以体现滚动)。
每一层都由一个Thickness定义上、右、下、左四条边的宽度。Content Area 被画成一个独立的容器,Viewport 相当于"探入"其中的一个窗口——在示例中 Viewport 位于 Content Area 的 (5,5) 处,直观展示了滚动的工作方式。
二、Frame:View 的"外框"
Frame是Rectangle(Location: Point+Size: Size),定义 View 相对于其 SuperView 的位置和尺寸。在源码 View.Layout.cs 中,Frame是一个可读写的Rectangle属性:
- 直接设置
Frame会把X、Y、Width、Height四个声明式属性覆盖为绝对值; - 改变
Frame会触发NeedsLayout与NeedsDraw,使 View 在下一个迭代周期中被重新布局和重绘; SetFrame是_frame字段唯一合法的直接写入点,同时负责把 Margin 的 Frame 同步为Frame with { Location = Point.Empty }(见 View.Layout.cs),其余 Adornment(Border、Padding)的 Frame 都基于 Margin 的 Frame 动态推导。
FrameChanged事件是观察尺寸/位置变化的权威事件:无论通过赋值X/Y/Width/Height、直接设置Frame,还是布局阶段解析相对Dim/Pos(如DimFill)引起的最终尺寸变化,都会触发它。从源码结构看,这也是处理"用事件驱动布局联动"时的首选钩子。
三、三种 Adornment:Margin、Border、Padding
3.1 Margin —— 最外层间隔
Margin是 View 相对同 SuperView 下其他 SubView 的外间距,把 Viewport 从 Frame 向内偏移。在 View.Adornments.cs 中声明为只读属性:
public Margin Margin { get; } = new ();典型情况下 Margin 是透明的;但可以通过显式设置Scheme覆盖。启用ShadowStyle(阴影样式)时,Margin 的 Thickness 会自动扩展以容纳右侧与底部的阴影——ShadowStyle本质上是操作 Margin 的便捷属性:设置非None值等价于同时设置 Margin 的Thickness与ShadowStyle;设置None则把 Margin 的 Thickness 清零(见 View.Adornments.cs)。
3.2 Border —— 边框线与标题
Border绘制视觉边框(使用画线字形 line-drawing glyphs)与Title,位于 Margin 与 Padding 之间。它在 View.Adornments.cs 声明:
public Border Border { get; } = new ();关键行为:
- 向内扩展:若
Border.Thickness.Top == 2,边框和标题占据前两行,从而缩小 Viewport; - Tab 头部:当
Border.Settings包含BorderSettings.Tab时,Border 会在TabSide指定的一侧渲染 Tab 页签头; - 边框样式便捷属性:
BorderStyle是一个原子化设置LineStyle与Thickness的辅助属性。LineStyle.Single/Rounded/Double会把Border.Thickness设为 1(若当前为 0)并设置对应线型;LineStyle.None则把 Thickness 清零(见 View.Adornments.cs)。效果示意:
// Single: ┌┤Title├──┐ // Rounded: ╭┤Title├──╮ // Double: ╔═Title═══╗- 对于 Tab 模式、渐变边框、逐边自定义厚度等高级需求,应直接操作
Border对象本身,例如:
view.BorderStyle = LineStyle.Rounded; view.Border.Settings = BorderSettings.Tab | BorderSettings.Title; view.Border.TabSide = Side.Top; view.Border.Thickness = new Thickness (1, 3, 1, 1);3.3 Padding —— 最内层内边距与滚动条
Padding位于最内层,将 Viewport 从 Border 继续向内偏移,是滚动条(Scrollbar)的绘制层。声明见 View.Adornments.cs:
public Padding Padding { get; } = new ();3.4 三层厚度的求和
View 提供了GetAdornmentsThickness()方法(View.Adornments.cs),返回 Margin、Border、Padding 三层厚度的总和:
public Thickness GetAdornmentsThickness () { var result = Thickness.Empty; result += Margin.Thickness; result += Border.Thickness; result += Padding.Thickness; return result; }这是"Viewport 相对 Frame 偏移多少"的直接答案:Viewport 相对 Frame 的偏移量 = 三层 Adornment 厚度之和。修改任何一层 Adornment 的尺寸都会改变Frame的解析结果,并触发SetNeedsLayout,让 SuperView 及其 SubViews 重新布局。
四、AdornmentImpl 的源码级实现:轻量设置 + 懒加载视图
三种 Adornment 共享的基类是AdornmentImpl(见 AdornmentImpl.cs),其设计要点对理解整个组合模型至关重要:
- Thickness 即数据:
AdornmentImpl持有Thickness属性和可选的Parent(所属 View)引用,ThicknessChanged事件在厚度变化时触发; - AdornmentView 懒加载:完整渲染能力(绘制、SubViews、鼠标、Arrangement、阴影)由
AdornmentView提供,但它仅在真正需要时才通过GetOrCreateView()创建(AdornmentImpl.cs)。创建时会同步父 View 的ViewportSettings、当前Frame以及BeginInit/EndInit状态,避免"先设值后创建"导致的状态丢失; - 几何计算不依赖视图存在:
Contains(in Point location)、FrameToScreen()等方法在AdornmentView尚未创建时也能基于Parent.Frame与Thickness直接完成命中测试与屏幕坐标换算,这正是 Margin/Border/Padding 可以作为纯设置项参与布局的前提。
五、Viewport 与 Content Area:滚动的实现机制
5.1 概念辨析
- Content Area:内容的实际绘制空间,其
Location恒为 (0,0)。当没有显式设置内容大小时,Content Area 就等于 Viewport; - Viewport:Content Area 中当前可见的"窗口"。当 Content Area 大于 Viewport 时,Viewport 就像一台摄像机,通过自身
Location在 Content Area 内平移来实现滚动。
文档中的示例将 Viewport 放在 Content Area 的 (5,5) 位置,直观说明了滚动偏移的来源——Viewport.Location即滚动偏移量。
5.2 Content Size 的读取与设置
View 通过 View.Content.cs 暴露内容尺寸 API:
GetContentWidth()/GetContentHeight():若未通过SetContentWidth/SetContentHeight显式设置,则回退返回Viewport的宽/高;GetContentSize():组合二者返回Size;SetContentSize(Size?):显式声明内容尺寸。一旦设置,Viewport就代表内容中当前可见的部分,从而启用"虚拟滚动"(virtual scrolling);同时DimAutoStyle.Content会根据 ContentSize 计算 View 的尺寸。
因此滚动工作流是:SetContentSize声明"内容有多大",然后通过改变Viewport.Location(等价于滚动偏移)选择"看哪一块"。Scrollbar 类视图正是在 Padding 层绘制当前偏移比例,见 Terminal.Gui/Views/ScrollBar。
5.3 布局联动
当内容尺寸未显式设置、View 也没有可见 SubViews 时,GetContentSize()直接返回 Viewport 尺寸且ContentSizeTracksViewport为true。这意味着默认情况下"内容有多大,视口就有多大";只有显式设置内容尺寸或存在需要容纳的 SubViews 时,Content 才会大于 Viewport、滚动才有意义。相关布局细节可继续阅读 layout.md 与 scrolling.md。
六、组合模型的实战速查
把六层结构落实到代码中,一个典型的带边框、可滚动的 View 大致如下:
var view = new View { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill (), // 最外层:Frame 由 X/Y/Width/Height 解析得出 }; // Margin:与兄弟视图的外间距(默认透明) view.Margin.Thickness = new Thickness (1, 1, 1, 1); // Border:边框线 + 标题(向内扩展,缩小 Viewport) view.BorderStyle = LineStyle.Rounded; // 便捷属性:LineStyle + Thickness=1 view.Title = "Composition Demo"; // Padding:内边距,滚动条所在层 view.Padding.Thickness = new Thickness (0, 1, 0, 0); // Content Area:声明内容大于 Viewport,启用滚动 view.SetContentSize (new Size (200, 100)); view.Viewport = view.Viewport with { Location = new Point (5, 5) }; // 相当于滚动到 (5,5)要点回顾:
- 几何换算:
Frame由Margin + Border + Padding逐层向内收缩得到Viewport,总偏移量 =GetAdornmentsThickness(); - 内容与可见性分离:
SetContentSize决定 Content Area,Viewport.Location决定可见窗口,二者之差就是滚动能力; - 声明式与命令式并存:
Frame/BorderStyle/ShadowStyle是便捷入口,复杂场景(Tab 边框、渐变、逐边厚度)直接操作Margin/Border/Padding对象; - 懒加载不影响几何:Adornment 的 View 层在需要绘制/交互时才创建,但厚度与命中测试始终可用(AdornmentImpl.cs)。
更完整的组合语义定义见 view-composition.md,布局与滚动机制的深入讲解可继续阅读仓库中的 layout.md、scrolling.md 以及 View.md。
- UI组件
- 跨平台
- 桌面应用
【免费下载链接】Terminal.Gui
Cross Platform Terminal UI toolkit for .NET
相关推荐
Puppeteer ElementHandle.boxModel() 详解:获取元素完整盒模型(content/padding/border/margin 四框)
Puppeteer ElementHandle.boxModel 详解:获取元素完整盒模型(content/padding/border/margin 四框)
浏览器控制测试网页爬虫开发工具Terminal.Gui 布局词汇表与概念精解:从 Frame、Adornment 到 Viewport 的完整布局体系
Terminal.Gui 布局词汇表与概念精解:从 Frame、Adornment 到 Viewport 的完整布局体系 Terminal.Gui 的布局系统是
UI组件跨平台桌面应用Terminal.Gui 滚动机制详解:Content Area、Viewport 与 ScrollBar 术语体系实战指南
Terminal.Gui 滚动机制详解:Content Area、Viewport 与 ScrollBar 术语体系实战指南 滚动是终端 UI(TUI)开发中最
UI组件跨平台桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考