自 GTK 设计之初,容器和布局策略一直是其核心组成部分。如果您希望您的部件根据特定策略布局其子部件,您必须实现 GtkContainer
来处理子部件的添加、删除和迭代,然后您必须实现 GtkWidget
中的尺寸协商虚拟函数来测量、定位和调整每个子部件的大小。
GTK 4 开发周期的主要主题之一是将更多功能委托给辅助对象,而不是将其编码到 GTK 提供的基类中。例如,我们将事件处理从 GtkWidget
描述的信号处理程序移动到事件控制器,并将渲染委托给 GtkSnapshot
对象。朝着这个方向迈出的另一步是将布局机制从 GtkWidget
本身解耦到一个辅助类型 GtkLayoutManager
。
布局管理器
布局管理器是负责测量和调整部件及其子部件大小的对象。每个 GtkWidget
都拥有一个 GtkLayoutManager
,并使用它来代替 measure()
和 allocate()
虚拟函数——这些函数将被弃用。更改的要点是:与其子类化 GtkWidget
来实现其布局策略,不如子类化 GtkLayoutManager
,然后将布局管理器分配给部件。
就像旧的 GtkWidget
代码一样,您需要覆盖一个虚拟函数来测量布局,称为 measure()
,它取代了 GTK 3 的 get_preferred_*
系列虚拟函数
static void layout_measure (GtkLayoutManager *layout_manager, GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline)
测量后,您需要将大小分配给布局;这发生在 allocate()
虚拟函数中,它取代了以前 GTK 主要版本的 size_allocate()
虚拟函数
static void layout_allocate (GtkLayoutManager *layout_manager, GtkWidget *widget, int width, int height, int baseline)
在更深奥的方面,您还可以覆盖 get_request_mode()
虚拟函数,该函数允许您声明布局管理器是请求固定大小,还是其一个大小取决于相反的大小,例如高宽或宽高
static GtkSizeRequestMode layout_get_request_mode (GtkLayoutManager *layout_manager, GtkWidget *widget)
您可能会注意到,每个虚拟函数都会传递布局管理器实例,以及使用该布局管理器的部件。
当然,这对 GTK 部件的工作方式的各个方面产生了更大的影响,最明显的是,布局代码的所有复杂性现在可以限制在其自己的对象中,通常是不可导出的,而部件可以保持可导出并变得更简单。
这项工作的另一个特点是,如果您想更改容器的布局策略,可以在运行时更改布局管理器;您还可以拥有每个部件的布局策略,而无需为部件代码添加更多复杂性。
最后,布局管理器允许我们摆脱 GTK 的特殊情况之一,即:容器子属性。
子属性
在 GtkContainer
的内部深处,存在着本质上是 GObject 属性相关代码的副本,其唯一的工作是为从 GtkContainer
派生的类型实现“子”属性。这些容器/子属性仅在子项被父级设置为特定容器类时存在,并用于各种原因,但通常用于控制布局选项,例如盒子和类盒子容器中的打包方向; GtkFixed
内的固定位置;或笔记本标签部件的展开/填充规则。
子属性很难使用,因为它们需要临时的 API 而不是通常的 GObject
API,因此需要在 GtkBuilder
、gtk-doc 和语言绑定中进行特殊处理。子属性也附加到容器的实际直接子项,因此如果一个部件插入一个子项(例如,GtkScrolledWindow
或 GtkListBox
),那么您需要保留对该子项的引用,以便更改应用于您自己的部件的布局。
在 GTK 的主分支中,我们摆脱了其中大部分——要么在存在实现相同功能的实际部件 API 时简单地删除它们,要么创建辅助 GObject 类型并将子属性移动到这些类型。最终目标是在 GTK 4 发布时删除所有这些属性以及 GtkContainer
中的相关 API。对于与布局相关的属性,GtkLayoutManager
提供了自己的 API,以便在将子项添加到或从使用布局管理器的部件中删除时,自动创建和销毁对象。创建的对象是可自省的,在文档或绑定方面不需要特殊处理。
您从派生自己的 GtkLayoutChild
类类型开始,并像为任何其他 GObject
类型一样添加属性。然后,您覆盖 GtkLayoutManager
的 create_layout_child()
虚拟函数
static GtkLayoutChild * create_layout_child (GtkLayoutManager *manager, GtkWidget *container, GtkWidget *child) { // The simplest implementation return g_object_new (your_layout_child_get_type (), "layout-manager", manager, "child-widget", child, "some-property", some_property_initial_state, NULL); }
之后,只要部件仍在使用布局管理器的容器的子项,您就可以访问您的布局子对象;如果子项从其父项中删除,或者容器更改了布局管理器,则布局子项会自动收集。
新的布局管理器
当然,仅仅在 GTK 中拥有 GtkLayoutManager
类对我们没有任何好处。GTK 4 为应用程序和部件开发人员引入了各种布局管理器
GtkBinLayout
实现了GtkBin
的布局策略,并增加了一个特点,即它支持多个彼此堆叠的子项,类似于GtkOverlay
的工作方式。您可以使用每个部件的对齐和展开属性来控制其在分配区域内的位置,并且GtkBinLayout
将始终请求分配其最大子项所需的最大空间。GtkBoxLayout
是GtkBox
实现的布局策略的直接移植;GtkBox
本身已移植为在内部使用GtkBoxLayout
。GtkFixedLayout
是GtkFixed
和GtkLayout
的固定布局定位策略的移植,并增加了允许您定义通用变换的功能,而不是每个子项的纯 2D 转换;GtkFixed
已被修改为使用GtkFixedLayout
并使用 2D 转换——并且GtkLayout
已合并到GtkFixed
中,因为它唯一的区别功能是GtkScrollable
接口的实现。GtkCustomLayout
是一个方便的布局管理器,它接受曾经是 GtkWidget 虚拟函数覆盖的函数,它主要用于在将现有部件移植到布局管理器的未来时充当桥梁。
我们仍在实施 GtkGridLayout
并使 GtkGrid
在内部使用它,遵循与 GtkBoxLayout
和 GtkBox
相同的模式。GTK 内的其他部件也将沿途获得自己的布局管理器,但同时它们可以使用 GtkCustomLayout
。
最后一步是实现一个基于约束的布局管理器,这将使我们能够创建复杂、响应式的用户界面,而无需将部件打包到嵌套层次结构中。基于约束的布局值得单独写一篇博客文章,敬请期待!
这看起来是一个很大的改进!!
感谢所有为 GTK 工作的人。您的努力得到了赞赏。
问题:一旦新的布局管理器稳定并准备就绪,Glade 是否会在第一天就准备好利用新功能?还是我们必须等待 Glade ‘追赶上来’?
@JR:这是一个最好向 Glade 维护者提出的问题;一般来说,这取决于 Glade 移植到 GTK4 的速度。
应该可以通过 Glade UI 将现有布局管理器分配给 GTK 部件,但首先 Glade 需要知道哪些布局管理器类可用,以及如何显示它们。
GTK 中现有的容器部件和其他使用布局管理器的部件当然可以开箱即用。