今天,我们将有客座作者 Timm Bäder,Corebird 的维护者和 GTK+ 的贡献者,来谈谈在 GTK+ 4.0 中编写复合部件的变化。
(注意:这里的一些信息基于尚未合并到主分支的分支,但我相信它们会在不久的将来合并)
在 GTK+3 中,只有 GtkContainer 的子类可以拥有子部件。这对于我们所知道的“公共”容器子项来说很有意义,例如 GtkBox - 即开发人员可以任意添加、删除和重新排序子部件,而容器只负责布局。
然而,GTK+3 中还有一些更复杂的部件不继承自 GtkContainer,例如 GtkSpinButton 或 GtkSwitch。这些部件从来没有真正的 GtkWidget 子项。例如,考虑 GtkSpinButton 中的两个可点击区域。我在这里不称它们为“按钮”是有原因的,因为在 GTK+3 中,它们不是真正的 GtkButton 实例,因为 GtkSpinButton 不是 GtkContainer。相反,GtkSpinButton 必须绕过这个事实,为向上/向下区域创建两个 GdkWindow,然后在其中渲染两个图标;关心悬停和 CSS 状态;各种按钮向上/向下事件;以及 GdkWindow 的生命周期等等。为了绕过 GtkContainer 的要求,我们在 GTK+3 中引入了小工具(GtkCssGadget)。在样式方面,一个小工具对应于一个 CSS 框,因此表示 CSS 树中的一个节点。在部件方面,它们被用来为非容器部件提供“类似部件的”、可使用 CSS 设置样式的子项。
GtkWidget 的更改
当然,在 GTK+4 中支持这些用例需要进行大量的更改。我在这里不会列出所有的更改(特别是那些比较难看的,比如焦点处理),但我认为其中大多数对于应用程序开发人员和自定义部件作者来说都相当有趣和重要。总的来说,我们试图摆脱特殊情况,通过尽可能地重用部件来走更通用的道路。因此,与其使用 PangoLayout 来显示文本,部件应该使用 GtkLabel。如果你有一个具有类似按钮语义的可点击区域,请尝试使用 GtkButton。如果你想在水平或垂直方向上布局部件,请使用 GtkBox。这样,我们就有一个输入和渲染都可以操作的部件树。在实践中,这意味着主要摆脱部件内部使用的所有小工具,以及独立的 GtkCssNode 实例。
遍历子部件
- gtk_widget_get_first_child()
- gtk_widget_get_last_child()
- gtk_widget_get_prev_sibling()
- gtk_widget_get_next_sibling()
GtkWidget *widget; GtkWidget *child; for (child = gtk_widget_get_first_child (widget); child != NULL; child = gtk_widget_get_next_sibling (child)) { /* Do stuff with @child */ g_assert (gtk_widget_get_parent (child) == widget); }
向非容器父项添加部件
在 GTK+4 中,gtk_widget_set_parent() 仍然有效,并将部件添加到父项的子部件列表的末尾。但是,我们显然也希望管理子部件的顺序,以及我们在列表中添加新子项的位置,因此我们有:
- gtk_widget_insert_before()
- gtk_widget_insert_after()
部件 CSS 名称
转换后的部件示例
GtkSwitch

gtkswitch.c
代码。理论上,我们还有更多的功能,例如可以使用 GtkLabel 支持的 text-decoration CSS 属性的有限支持,但我只是怀疑这是否非常有用。GtkSpinButton

gtkspinbutton.c
中的另外 300 行代码。通过使用 GtkButton,旧的图标助手小工具也变成了实际的 GtkImage 实例。不幸的是,我们必须在这里自己实现一些 GtkGesture 的魔力,因为 GtkSpinButton 也支持在其按钮上进行鼠标中键和右键单击,而 GtkButton::clicked 仅对单个鼠标主键单击做出反应。GtkLevelBar
GtkProgressBar
GtkExpander
意外的 GtkBox 和 GtkButton 子类
通用重构规则和未来
testsuite/css/nodes.c
中的 CSS 节点测试。