自 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 主要版本的 venerable 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
。
最后一步是实现一个基于约束的布局管理器,这将使我们能够创建复杂的、响应式的用户界面,而无需将窗口小部件打包到嵌套层次结构中。基于约束的布局值得单独撰写一篇博文,敬请期待!