GtkColumnView

在我最近关于 GTK 4 中列表视图模型的系列文章中,我遗留了一个未完成的部分,即详细介绍 GtkColumnView。这很可能是本系列文章中最复杂的部分。我们正在进入 GtkTreeView 的核心地带——任何旨在替代其大部分功能的组件都将是一个复杂的庞然大物。

概述

就像我们对 GtkListView 所做的那样,我们将从一个高层次的概述和一个图片开始。

如果你回顾一下列表视图的图片,你会记得我们使用一个列表项工厂为模型中需要显示的每个项创建一个部件。

在列视图中,我们需要为每个项提供多个部件——每个列一个。我们实现这一目标的方法是为每个列提供自己的列表项工厂。每当我们需要显示一个新项时,我们将每个列工厂的部件组合成新项的行。

在内部,列视图实际上是使用列表视图来保存行。这很好,因为我在前一篇关于项重用以及如何使用列表项工厂的文章中解释的所有内容都适用。

当然,有些事情不同的。例如,列视图组织了尺寸分配,以便所有行中的部件对齐以形成正确的列。

注意:与 GtkListView 一样,列视图仅为当前在视图中的模型片段创建部件,因此它共享垂直可伸缩性。在水平方向上则不是这样——每一行都完全填充了每个列的部件,即使它们在左侧或右侧超出视图范围。因此,如果你添加大量的列,事情会变得缓慢。

标题和其他复杂性

列对象还包含其他数据,例如标题。列视图使用这些标题来显示每个列的标题。如果将列视图标记为可重新排序,则可以通过拖放标题部件来重新排列列。如果将列标记为可调整大小,则可以拖动两列之间的边框来调整它们的大小。

如果你注意到了,你现在可能会想知道这种调整大小如何与行中的单元格可以是任意部件这一事实结合在一起,这些部件期望至少有它们的最小尺寸可用于绘制其内容。答案是我们正在使用 GTK 4 渲染机制的另一个新功能:部件可以控制如何处理在其边界之外(由子部件)的绘制,使用

 gtk_widget_set_overflow (cell, GTK_OVERFLOW_HIDDEN)

排序、选择和追求树视图的对等性

由于我们希望在功能上与 GtkTreeview 匹配,因此我们还没有完成。用户喜欢在树视图中做的另一件事是单击标题,以按该列对内容进行排序。GtkColumnView 标题也允许这样做。

你可能还记得上一篇文章中,排序是通过将数据包装在 GtkSortListModel 中,并为其提供合适的排序器对象来完成的。由于我们希望根据你单击的列标题具有不同的排序顺序,因此我们为每个列提供其自己的排序器,你可以使用以下命令进行设置

gtk_column_view_column_set_sorter (column, sorter)

但是,我们如何从你刚刚单击的列中获取正确的排序器,并将其附加到排序模型?请记住,排序模型不会是我们传递给列视图的最外层模型,因为它始终是一个选择模型,因此列视图无法自行切换排序列表模型上的排序器。

我们提出的解决方案是使列视图提供一个排序器,该排序器在内部使用列排序器,使用

gtk_column_view_get_sorter (view)

你可以在设置模型时,将此排序器一次性提供给你的排序模型,然后在用户单击列标题以激活不同的列排序器时,事情将自动更新。

这听起来很复杂,但它的效果出奇的好。这种方法的一个好处是,我们实际上可以一次按多个列进行排序——因为我们拥有所有可用的列排序器,并且我们知道你最后单击了哪个。

相比之下,选择处理很容易。它的工作方式与 GtkListView 中的工作方式相同。

总结

GtkColumnView 是一个复杂的部件,但我希望本系列文章能让您更容易开始使用它。

关于列表模型

在上一个帖子中,我承诺深入了解列表模型以及 GTK 4 在此领域提供的功能。让我们首先看一下GListModel接口

struct _GListModelInterface
{
  GTypeInterface g_iface;

  GType    (* get_item_type) (GListModel *list);
  guint    (* get_n_items)   (GListModel *list);
  gpointer (* get_item)      (GListModel *list,
                              guint       position);
};

实现接口的一个重要部分是,你需要使用以下内容发出
::items-changed 信号,在使用 GLib 为此目的提供的帮助函数时
GLib 为此目的提供了帮助函数

void g_list_model_items_changed (GListModel *list,
                                 guint       position,
                                 guint       removed,
                                 guint       added)

关于此接口的一些注意事项

  • 它非常简洁;这使得它易于实现
  • 该 API 基于位置,并且仅处理列表成员资格的更改——跟踪项目本身的更改取决于你自己

列表模型动物园

GTK 附带了大量的列表模型实现。仔细检查后,它们分为几个不同的组。

列表模型构建工具包

第一组可以称为列表模型构建工具包:通过修改或组合你已有的模型来构建新模型的模型。

该组中的第一个模型是GtkSliceListModel,它获取现有模型的一部分,给定一个偏移量和一个大小,并创建一个仅包含这些项的新模型。如果你想在分页视图中显示一个大列表,这将非常有用——向前和向后按钮只会将偏移量增加或减少大小。切片模型还可以用于通过随着时间的推移使切片更大来逐步填充列表。GTK 在某些地方正在使用此技术。

该组中的下一个模型GtkFlattenListModel,它采用多个列表模型并将它们组合为一个。由于这都是关于列表模型的,因此要组合的模型以列表模型的形式传递给展平模型。每当你需要组合来自多个来源的数据时,这都非常有用,例如 GTK 在打印对话框中对纸张尺寸的处理。

Paper size list in print dialog
扁平化列表

请注意,原始模型继续存在于展平模型之后,并且它们的更新将按预期由展平列表模型传播。

有时,你的数据位于列表模型中,但形式不太正确。在这种情况下,你可以使用GtkMapListModel将原始模型中的每个项替换为不同的项。

具体模型

GTK 及其依赖项包括许多用于我们自己处理的数据类型的具体模型。

这里的第一个示例是 Pango 对象,它们为其数据实现列表模型接口:PangoFontMap 是 PangoFontFamily 对象的列表模型,而 PangoFontFamily 是 PangoFontFace 对象的列表模型。字体选择器正在使用这些模型。

font chooser dialog
Pango 列表模型

下一个示例是 GtkDirectoryListGtkBookmarkList 对象,它们将在文件选择器中用于表示目录内容和书签。关于这些有趣的一个细节是,它们都需要进行 IO 操作来填充其内容,并且它们会异步执行,以避免长时间阻塞 UI。

该组中的最后一个模型略微不太具体:GtkStringList 是一个围绕常见的字符串数组的简单列表模型包装器。这种列表模型将经常使用的一个示例是 GtkDropDown。这很常见,以至于 GtkDropDown 有一个方便的构造函数,它接受一个字符串数组并为你创建 GtkStringList

GtkWidget *
    gtk_drop_down_new_from_strings (const char * const * strings)

选择

下一组模型使用一个新接口扩展了 GListModel:GtkSelectionModel。对于底层模型中的每个项,GtkSelectionModel 维护着它是否被选中的信息。

我们将不详细讨论该接口,因为你不太可能需要自己实现它,但是最重要的几点是

gboolean gtk_selection_model_is_selected (GtkSelectionModel *model)
                                          guint              pos)
GtkBitset *
       gtk_selection_model_get_selection (GtkSelectionModel *model)

因此,你可以以位集的形式获取单个项或整体的选择信息。当然,还有一个 ::selection-changed 信号,它的工作方式与 GListModel 的 ::items-changed 信号非常相似。

GTK 有三种 GtkSelectionModel 实现:GtkSingleSelectionGtkMultiSelectionGtkNoSelection,它们在可以同时选择的项目数量上有所不同(1 个、多个或 0 个)。

GtkGridView 颜色演示展示了多选的实际效果,带有橡皮筋效果

 

当您使用 GTK 的新列表小部件时,您很可能会遇到选择模型,因为它们都要求其模型是选择模型。

重要的模型

我想提到的最后一组模型是那些执行您在列表中期望的典型操作的模型:过滤和排序。这些模型是 GtkFilterListModelGtkSortListModel。它们都使用辅助对象来实现其操作:GtkFilter 和 GtkSorter。两者都有子类来处理常见情况:排序和过滤字符串或数字,或使用回调。

在 GTK 3.99 发布之前,我们在这两个模型上花费了大量精力,并使它们以增量方式工作,以避免在处理大型模型时长时间阻塞 UI。

GtkListView 单词演示展示了对 500,000 个单词列表的交互式过滤

剩余的模型

GTK 中还有一些列表模型实现,它们不太适合上述任何一组,例如 GtkTreeListModelGtkSelectionFilterModelGtkShortcutController。今天我将跳过这些。

模型无处不在

最后,我将简要列出返回列表模型的 GTK API

  • gdk_display_get_monitors
  • gtk_widget_observe_children
  • gtk_widget_observe_controllers
  • gtk_constraint_layout_observe_constraints
  • gtk_constraint_layout_observe_guides
  • gtk_file_chooser_get_files
  • gtk_drop_down_get_model
  • gtk_list_view_get_model
  • gtk_grid_view_get_model
  • gtk_column_view_get_model
  • gtk_column_view_get_columns
  • gtk_window_get_toplevels
  • gtk_assistant_get_pages
  • gtk_stack_get_pages
  • gtk_notebook_get_pages

总而言之,列表模型在 GTK 4 中无处不在。它们灵活有趣,您应该使用它们!

GtkListView 入门

一些 GTK4 的早期采用者指出,新的列表小部件并非最容易学习的。特别是,GtkExpression 和 GtkBuilderListItemFactory 很难理解。这并不太令人惊讶 – 一个完整的列表小部件,带有列、选择、排序和树结构等,是一个复杂的野兽。

但是,让我们看看是否可以逐一分解,并使其更容易理解。

概述

让我们从相关组件及其交互的高级视图开始:模型、列表项工厂和列表视图。

它们是创建列表视图时发生的三件事

view = gtk_list_view_new (model, factory);

我们使用的模型是 GListModels。这些模型始终包含 GObject,因此您必须以对象的形式提供数据。这是与 GtkTreeview 的第一个显着区别,后者直接使用包含基本类型的 GtkTreeModels。

对于某些简单的情况,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 作为参数,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 模板指定为资源,而不是直接嵌入它。

深入了解

我们今天在这里描述的所有内容也适用于网格视图,只需进行少量调整。

到目前为止,我们专注于事物的视图方面。关于模型也有很多要说的。

然后是列视图,它值得单独写一篇帖子。

关于 GTK 4 中列表的更多信息

上一篇文章介绍了 3.98.5 版本中引入的整体列表视图框架,并展示了一些示例。在这篇文章中,我们将深入研究一些技术领域,并研究一些相关主题。

GtkTreeView 的一个重要特性是它可以显示树。毕竟,这就是它的命名原因。GtkListView 侧重于纯列表,并使处理这些列表更容易。特别是,GListModel api 比 GtkTreeModel 简单得多 - 这就是为什么自定义树模型实现相对较少,但 GTK 4 已经有一整套列表模型。

但是,我们有时仍然需要显示树。这方面存在一些复杂性,但我们已经找到了一种方法来实现它。

模型

我们需要的第一个要素是模型。由于 GListModel 表示项的线性列表,因此我们必须更加努力地使其处理树。

GtkTreeListModel 通过提供一种按需创建新的子列表模型来展开项的方法来做到这一点。GtkTreeListModel 中的项是 GtkTreeListRow 的实例,它包装模型中的实际项,并且有一些函数(例如 gtk_tree_list_row_get_children())用于获取子模型的项和 gtk_tree_list_row_get_item() 用于获取原始项。GtkTreeListRow 具有一个 :expanded 属性,用于跟踪子项当前是否显示。

树列表模型的核心是 GtkTreeListModelCreateModelFunc,它从您的列表中获取一个项,并返回一个新的列表模型,其中包含与树中给定项的子项类型相同的项。

这是一个 GSettings 对象的树列表模型的示例。该函数枚举给定 GSettings 对象的子设置,并为它们返回一个新的列表模型

static GListModel *
create_settings_model (gpointer item,
                       gpointer unused)
{
  GSettings *settings = item;
  char **schemas;
  GListStore *result;
  guint i;

  schemas = g_settings_list_children (settings);

  if (schemas == NULL || schemas[0] == NULL)
    {
      g_free (schemas);
      return NULL;
    }

  result = g_list_store_new (G_TYPE_SETTINGS);
  for (i = 0; schemas[i] != NULL; i++)
    {
      GSettings *child = g_settings_get_child (settings, schemas[i]);
      g_list_store_append (result, child);
      g_object_unref (child);
    }

  g_strfreev (schemas);

  return G_LIST_MODEL (result);
}

展开器

我们需要使用的下一个组件是一个可以显示展开箭头的小部件,用户可以通过点击它来控制 :expanded 属性。这个组件由 GtkTreeExpander 小部件提供。就像 GtkTreeListRow 项目包装模型中的底层项目一样,您可以使用 GtkTreeExpander 小部件来包装用于显示项目的其他小部件。

这是树形展开器在我们 GSettings 示例中的实际效果

完整的示例可以在这里找到。

排序

在我们离开树形结构之前,最后一个要讨论的主题是排序。列表通常有多种排序方式:a-z、z-a、忽略大小写等等。列视图通过允许您将排序器与列相关联来支持此功能,用户可以通过单击列标题来激活排序器。此 API 如下:

gtk_column_view_column_set_sorter (column, sorter)

在对树形结构进行排序时,您通常希望排序顺序应用于树中给定级别的项目,而不是跨级别应用,因为那样会打乱树形结构。GTK 通过 GtkTreeListRowSorter 来支持此功能,它包装现有的排序器,并使其尊重树形结构。

sorter = gtk_column_view_get_sorter (view);
tree_sorter = gtk_tree_list_row_sorter_new (sorter);
sort_model = gtk_sort_list_model_new (tree_model,           
                                      tree_sorter);
gtk_column_view_set_model (view, sort_model);

总而言之,新的列表小部件已经稍微弱化了树形结构,并且它们给机制增加了很多复杂性,但是它们在列表视图和列视图中都得到了完全的支持

组合框

单元格渲染器被使用的更麻烦的领域之一是我们的单选控件:GtkComboBox。它从来都不是一个很好的选择,特别是在与嵌套菜单结合使用时。因此,我们很期待尝试在 GtkComboBox 的替代品上使用新的列表视图机制。

从设计方面来说,长期以来一直希望能够更好地处理组合框,正如 2015 年的这个模型中所看到的

五年后,我们终于有了一个替代小部件。它被称为 GtkDropDown。新小部件的 API 尽可能地简洁,几乎所有工作都由列表模型和项目工厂机制完成。基本上,您使用 gtk_drop_down_new() 创建一个下拉菜单,然后为其提供一个工厂和一个模型,就完成了。

由于大多数选择都由简单的字符串组成,因此有一个方便的方法可以从字符串数组中为您创建模型和工厂

const char * const times[] = {
  "1 minute",
  "2 minutes",
  "5 minutes",
  "20 minutes",
  NULL
};

button = drop_down_new ();
gtk_drop_down_set_from_strings (GTK_DROP_DOWN (button), times);

这个便捷的 API 与 GtkComboBoxText 非常相似,GtkBuilder 的支持也非常相似。您可以在 ui 文件中指定字符串列表,如下所示

<object class="GtkDropDown">
  <items>
    <item translatable="yes">Factory</item>
    <item translatable="yes">Home</item>
    <item translatable="yes">Subway</item>
  </items>
</object>

以下是一些 GtkDropDowns 的实际操作

总结

再次强调这一点:所有这些都是全新的 API,我们很乐意听到您对哪些效果良好、哪些效果不佳以及缺少哪些功能的反馈。

GTK 4 中的可伸缩列表

GTK 4 中最后一个缺失的重要部分是新的列表和小部件的基础设施。它刚刚被合并,并包含在 3.98.5 版本中。因此,现在是时候仔细看看了。

历史:树视图和列表框

自远古时代(即 GTK 2)以来,GtkTreeView 一直是 GTK 中首选的数据显示小部件。它使用模型-视图模式将数据与显示基础设施分开。多年来,它衍生了一个网格显示同级项 (GtkIconView) 和一个选择表亲 (GtkComboBox),使用相同的基础设施(树模型和单元格渲染器)。

不幸的是,GtkTreeView 中使用单元格渲染器进行渲染的方法给我们留下了裂痕:小部件使用一组 vfuncs 和技术进行大小分配和渲染,而单元格渲染器使用另一组。这种分裂的不幸后果之一是,很难在树视图中进行动画(因为单元格渲染器不保留状态)。另一个后果是,GTK CSS 渲染机制的大部分进步在单元格渲染器中都不可用。

因此,我们非常希望使用小部件来显示列表中的数据。在 GTK 3 时代,我们为此引入了许多容器:用于列表的 GtkListBox 和用于网格的 GtkFlowBox。它们不使用单元格渲染器,因此上述限制不是问题。它们甚至可以使用列表模型来保存数据。但是它们为每个数据项生成一个小部件,这严重限制了它们的可伸缩性。根据经验,GtkListBox 可以很好地处理 1000 个项目,而 GtkTreeView 对于 100,000 个项目可以正常工作。

在长期以来,克服可伸缩性限制,同时仍然使用小部件进行所有渲染一直是我们的路线图上的一个目标。

可伸缩性限制
小部件 可伸缩性
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 属性绑定到 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 的目标。