GTK 4 中最后缺失的重要部分之一是新的列表和网格部件的基础设施。它刚刚被合并,并包含在 3.98.5 版本中。因此,现在是时候仔细看看了。
历史:树形视图和列表框
自古以来(即 GTK 2),GtkTreeView 一直是 GTK 中首选的数据显示部件。它使用模型-视图模式将数据与显示基础设施分离。多年来,它衍生出了一个网格显示兄弟(GtkIconView)和一个选择表亲(GtkComboBox),使用相同的基础设施(树模型和单元格渲染器)。
不幸的是,GtkTreeView 中使用单元格渲染器进行渲染的方法给我们留下了一个分裂:部件使用一组 vfunc 和技术进行大小分配和渲染,而单元格渲染器使用另一组。这种分裂的一个不幸后果是,很难在树形视图中进行动画(因为单元格渲染器不保留状态)。另一个后果是,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。
传统上返回各种项目 GLists 的 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 属性绑定到 label 属性。在 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 相匹配。
总结
这篇文章介绍了新的列表部件。我们这里没有涉及到更多内容,例如树或组合框替换。要了解有关新 API 的更多信息,请访问 GTK 文档中的 详细介绍。
我们现在已经将列表视图基础设施合并到主分支。这并不意味着它已完成。但我们认为它已准备好进行更广泛的使用,我们希望收到您的反馈,了解哪些工作正常,哪些工作不正常,以及缺少什么。
而且,要明确的是,这并不意味着我们正在从 GTK 4 中删除树形视图和组合框——现在为时已晚,它们仍然在 GTK 内部的许多地方使用。这可能是 GTK 5 的目标。