一些 GTK4 的早期采用者指出,新的列表部件不太容易学习。特别是,GtkExpression 和 GtkBuilderListItemFactory 很难理解。这并不奇怪——一个完整的列表部件,包含列、选择、排序和树结构等,是一个复杂的组件。
但是,让我们看看是否可以逐一解开这些问题,使其更容易理解。
概述
让我们从相关组件及其交互的高级视图开始:模型、列表项工厂和列表视图。
它们是创建列表视图时出现的三个要素。
view = gtk_list_view_new (model, factory);
我们使用的模型是 GListModel。它们总是包含 GObject,因此您必须以对象的形式提供数据。这是与 GtkTreeView 的第一个显着差异,后者直接使用包含基本类型的 GtkTreeModel。
对于某些简单的情况,GTK 提供了现成的模型,例如 GtkStringList。但一般来说,您必须创建自己的模型。值得庆幸的是,GListModel 的接口比 GtkTreeModel 简单得多,因此这并不太难。

列表项工厂的职责是生成一个行部件,并在列表视图需要时将其连接到模型中的一个项。
列表视图将创建比填充其可见区域所需的行多一些的行,以便更好地估计滚动条的大小,并为决定滚动视图时提供一些“缓冲”。
一旦您滚动,我们不一定需要让工厂创建更多的行——我们可以回收在另一端滚出视图的行。

值得庆幸的是,所有这些都在幕后自动发生。您所要做的就是提供一个列表项工厂。
创建项
GTK 提供了两种不同的创建项的方法。您可以使用 GtkSignalListItemFactory 手动创建,也可以使用 GtkBuilderListItemFactory 从 ui 文件实例化您的行部件。
手动方法更容易理解,所以让我们先看看它。
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", setup_listitem_cb, NULL);
g_signal_connect (factory, "bind", bind_listitem_cb, NULL);
当工厂需要创建一个新的行部件时,会发出“setup”信号;当一个行部件需要连接到模型中的一个项时,会发出“bind”信号。
这两个信号都将 GtkListItem 作为参数,它是一个包装对象,可以让您访问模型项(使用 gtk_list_item_get_item()),还可以让您传递新的行部件(使用 gtk_list_item_set_child())。
static void
setup_listitem_cb (GtkListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label = gtk_label_new ("");
gtk_list_item_set_child (list_item, label);
}
通常,您的行会比单个标签更复杂。您可以创建复杂的部件并将它们分组到容器中,根据需要。
static void
bind_listitem_cb (GtkListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
MyObject *obj;
label = gtk_list_item_get_child (list_item);
obj = gtk_list_item_get_item (list_item);
gtk_label_set_label (GTK_LABEL (label),
my_object_get_string (obj));
}
如果您的“bind”处理程序连接到项上的信号或执行其他需要清理的操作,您可以使用“unbind”信号来执行该清理。“setup”信号有一个类似的对应信号,称为“teardown”。
构建器方式
我们的“setup”处理程序基本上是创建小型部件层次结构的配方。GTK 有一种更声明性的方式来执行此操作:GtkBuilder ui 文件。这就是 GtkBuilderListItemFactory 的工作方式:您给它一个 ui 文件,它会在需要创建行时实例化该 ui 文件。
ui = "<interface><template class="GtkListItem">...";
bytes = g_bytes_new_static (ui, strlen (ui));
gtk_builder_list_item_factory_new_from_bytes (scope, bytes);
您现在可能会想:等一下,您正在为模型中的数千个项解析 xml 文件,这难道不昂贵吗?
对此问题有两个答案
- 我们不是为每个项字面解析 xml;我们解析一次,存储回调序列,然后在以后重放它。
- GtkBuilder 最昂贵的部分实际上不是 xml 解析,而是对象的创建;回收行有助于解决这个问题。
相对容易理解的是,ui 文件如何替换“setup”处理程序,但是“bind”呢?在上面的示例中,bind 回调正在获取项的属性(MyObject:string 属性)并使用它们的值来设置部件的属性(GtkLabel:label 属性)。换句话说,“bind”处理程序正在执行属性绑定。为了简单起见,我们在这里只创建了一次性绑定,但我们也可以使用 g_object_bind_property() 创建持久绑定。
GtkBuilder ui 文件可以在对象之间设置属性绑定,但有一个问题:模型项在 ui 文件中不是“存在”的,它只在“bind”时稍后与行部件关联。
这就是 GtkExpression 的用武之地。GtkExpression 的核心是一种描述尚未存在对象之间绑定的方法。在我们的例子中,我们想要实现的是
label->label = list_item->item->string
不幸的是,当它作为 ui 文件的一部分转换为 xml 时,会变得更加笨拙。
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkLabel">
<binding name="label">
<lookup name="string">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
请记住,ui 模板中的类名 (GtkListItem) 用作引用正在实例化的对象的“this”指针。
因此,<lookup name=”item”>GtkListItem</lookup> 表示:创建的列表项的“item”属性的值。<lookup name=”string”> 表示:该对象的“string”属性。<binding name=”label”> 表示将部件的“label”属性设置为该属性的值。
由于表达式的工作方式,当列表项工厂将列表项的“item”属性设置为新值时,所有这些都将重新评估,这正是我们需要使行部件回收工作的原因。
专家级困惑
当您嵌套事物时,GtkBuilderListItemFactory 和 GtkExpression 会变得非常令人困惑——列表项工厂本身可以在 ui 文件中构造,并且它们可以被赋予自己的 UI 文件作为属性,因此您最终会得到如下结构
<object class="GtkListView">
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
...
]]>
</property>
</object>
</property>
...
即使是 GTK 专家也可能会感到困惑。
我的建议是,在开始使用 GtkListView 时避免这种情况——您不必在 UI 文件中创建列表项工厂,并且您可以将其 UI 模板指定为资源而不是直接嵌入它。
深入了解
我们今天在这里描述的所有内容也适用于网格视图,只需进行最少的调整。
到目前为止,我们专注于事物视图方面。关于模型也有很多要说的。
然后是列视图,它值得单独写一篇文章。