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
























