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