GTK 4 中最后缺少的一大块是用于新的列表和小部件的基础设施。它刚刚被合并,并包含在 3.98.5 版本中。因此,现在是时候仔细研究一下了。
历史:树状视图和列表框
自远古时代(即 GTK 2)以来,GtkTreeView 一直是 GTK 中常用的数据展示小部件。它使用模型-视图模式来保持数据与显示基础设施的分离。多年来,它增长了一个网格显示兄弟(GtkIconView)和一个选择堂兄弟(GtkComboBox),它们使用相同的基础设施(树模型和单元格渲染器)。
不幸的是,GtkTreeView 中使用单元格渲染器进行渲染的方法导致了分裂:小部件使用一组 vfuncs 和技术进行大小分配和渲染,而单元格渲染器则使用另一组。这种分裂的一个不幸后果是,很难在树状视图中进行动画(因为单元格渲染器不保留状态)。另一个后果是,GTK CSS 渲染机制的大部分进展在单元格渲染器中都不可用。
因此,我们非常希望使用小部件来显示列表中的数据。在 GTK 3 时代,我们为此引入了许多容器:GtkListBox 用于列表,GtkFlowBox 用于网格。它们不使用单元格渲染器,因此上述限制不是问题。它们甚至可以使用列表模型来保存数据。但是它们为每个数据项生成一个小部件,这严重限制了它们的可伸缩性。根据经验,GtkListBox 可以很好地处理 1000 个项目,而 GtkTreeView 可以很好地处理 100000 个项目。
在仍然使用小部件进行所有渲染的同时,克服可伸缩性限制一直是我们的长期路线图。
小部件 | 可伸缩性 |
---|---|
GtkIconView | 100 |
GtkListBox | 1 000 |
GtkTreeView | 100 000 |
GtkListView | 无限 |
新的基础设施
通过列表视图系列小部件,我们希望最终实现这一目标。目标是很好地处理无限数量的项目。如果您使用过像 Android 回收器视图之类的东西,您会认出列表视图背后的基本思想
- 项目的数据以模型(包含对象)的形式提供
- 仅为可查看的项目范围创建小部件
- 可以通过将小部件绑定到不同的项目来回收小部件
- 尽可能避免遍历模型中的所有项目,而只处理绑定到小部件的可视范围内的项目
模型
对于我们的模型-视图架构的模型方面,我们已经放弃了 GtkTreeModel,现在使用 GListModel。这有几个原因。其中一个原因是,我们希望这些项目是具有属性的对象,因此我们可以使用属性绑定。另一个原因是,GtkTreeModel 本身的可伸缩性并不高(例如,请参见此 案例中的意外二次行为)。
GTK 4 带有丰富的 GListModel 实现,从组合或修改现有模型的各种方式到过滤和排序。几个 GtkFilter 和 GtkSorter 类支持过滤和排序。选择也在模型端处理,使用 GtkSelectionModel 及其子类。
最后,还有用于处理的数据类型的具体模型:用于文件的 GtkDirectoryList,用于字体的 PangoFontMap。
传统上返回各种项目的 GList 的 API 已被更改或补充为返回 GListModel 的 API,例如 gdk_display_get_monitors()、gtk_window_get_toplevels() 和 gtk_stack_get_pages()。
工厂
由于我们正在讨论按需自动创建小部件,因此将涉及工厂。GtkListItemFactory 是负责为模型中的项目创建小部件的对象。
此工厂有不同的实现。其中之一,GtkBuilderListItemFactory,使用 ui 文件作为列表项小部件的模板。这是一个典型的示例
<interface> <template class="GtkListItem"> <property name="child"> <object class="GtkLabel"> <property name="xalign">0</property> <property name="wrap">1</property> <property name="width-chars">50</property> <property name="max-width-chars">50</property> <binding name="label"> <lookup name="summary" type="SettingsKey"> <lookup name="item">GtkListItem</lookup> </lookup> </binding> </object> </property> </template> </interface>
完整的示例可以在 gtk4-demo 中找到。
另一个列表项工厂实现 GtkSignalListItemFactory,采用回调来 设置、拆卸、绑定 和 解除绑定 来自项目的窗口小部件。
static void setup_listitem_cb (GtkListItemFactory *factory, GtkListItem *list_item) { GtkWidget *image; image = gtk_image_new (); gtk_image_set_icon_size (GTK_IMAGE (image), GTK_ICON_SIZE_LARGE); gtk_list_item_set_child (list_item, image); } static void bind_listitem_cb (GtkListItemFactory *factory, GtkListItem *list_item) { GtkWidget *image; GAppInfo *app_info; image = gtk_list_item_get_child (list_item); app_info = gtk_list_item_get_item (list_item); gtk_image_set_from_gicon (GTK_IMAGE (image), g_app_info_get_icon (app_info)); } static void activate_cb (GtkListView *list, guint position, gpointer unused) { GListModel *model; GAppInfo *app_info; model = gtk_list_view_get_model (list); app_info = g_list_model_get_item (model, position); g_app_info_launch (app_info, NULL, NULL, NULL); g_object_unref (app_info); } ... 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); list = gtk_list_view_new_with_factory (factory); g_signal_connect (list, "activate", activate_cb, NULL); model = create_application_list (); gtk_list_view_set_model (GTK_LIST_VIEW (list), model); g_object_unref (model); gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), list);
完整的示例可以在 这里 找到。
表达式
要将工厂创建的小部件绑定到项目中的数据,我们需要一种灵活的机制来绑定属性。GObject 的 GBinding 机制朝着正确的方向发展,但它不够灵活,无法处理您可能需要在子对象或小部件的层次结构深处绑定属性,以及相关对象甚至在您设置绑定时可能不存在的情况。
为了处理这个问题,我们引入了 GtkExpression 作为更灵活的绑定系统,可以表达诸如
label = this->item->value
其中 _this_ 是 GtkListItem,它具有 _item_ 属性(类型为 SettingsKey),我们希望将其 _value_ 属性绑定到标签属性。在 GtkBuilder ui 文件中表达相同的内容看起来有点笨拙
<binding name="label"> <lookup name="value" type="SettingsKey"> <lookup name="item">GtkListItem</lookup> </lookup> </binding>
新的小部件
GtkListView 是一个简单的列表,没有列或标题。使用这种列表的一个示例是 GtkFontChooser。GtkListView 开辟新天地的一个小方法是,它可以设置为水平列表以及通常的垂直方向。
GtkGridView 将小部件放置在一个可重排的网格中,很像 GtkFlowBox 或 GtkIconView。
GtkColumnView 相当于完整的 GtkTreeView,具有多列和标题,以及诸如交互式调整大小和重新排序等功能。就像 GtkTreeView 有 GtkTreeViewColumns 一样,GtkColumnView 具有 GtkColumnViewColumns 列表。每一列都有一个为每个项目生成单元格的工厂。然后将这些单元格组合成该项目的行。
示例
复杂 GTK 对话框中的许多列表(尽管并非所有列表)都已替换为新的列表小部件。例如,字体选择器现在使用 GtkListView,GTK 检查器中的大多数列表都使用 GtkColumnView。
但是 gtk4-demo 包含许多新小部件的示例。这里有一些
时钟示例显示了具有完整小部件渲染灵活性的优势。
颜色示例显示了以各种方式渲染的中型数据集。
设置示例表明,列视图在功能方面或多或少与 GtkTreeView 匹配。
总结
这篇文章介绍了新的列表小部件。还有更多我们在这里没有涉及的内容,例如树或组合框替换。了解新 apis 的一个地方是 GTK 文档中的 详细介绍。
我们现在已将列表视图基础设施合并到主分支。这并不意味着它已经完成。但是我们认为它已准备好进行更广泛的使用,并且我们希望得到您对哪些有效、哪些无效以及缺少哪些内容的反馈。
并且,要明确的是,这并不意味着我们正在从 GTK 4 中删除树状视图和组合框——为时已晚,它们仍然在 GTK 内部的许多地方使用。这可能是 GTK 5 的目标。