跳转到内容

GTK+ 示例/树视图/自定义模型

来自 Wikibooks,开放世界中的开放书籍

编写自定义模型

[编辑 | 编辑源代码]

何时使用自定义模型?

[编辑 | 编辑源代码]

自定义树模型使您可以完全控制您的数据以及数据如何向外部表示(例如,向树视图小部件)。它具有以下优势:您可以完全按照自己的需要存储、访问和修改数据,并且可以优化数据的存储和检索方式,因为您可以编写自己的函数来访问数据,而不必依赖于 gtk_tree_model_get。定制的模型可能比 GTK 提供的通用列表和树存储更快,因为通用存储的设计目标是灵活性和通用性。

在您已将所有数据存储在外部树状结构(例如 libxml2 XML 树)中,并且只想显示该结构时,自定义模型也很有用。然后,您可以编写一个自定义模型将该结构映射到树模型(但这可能并不像看起来那么简单)。

使用自定义模型,您还可以实现一个过滤器模型,该模型只根据某些过滤器标准显示某些行,而不是显示所有行(Gtk+-2.4 有一个过滤器模型 GtkTreeModelFilter,可以做到这一点以及更多,但您可能仍然希望自己实现它。如果您需要在 Gtk-2.0 或 Gtk-2.2 中使用 GtkTreeModelFilter,请查看本教程的代码示例——其中有 GuiTreeModelFilter,它基本上只是原始的 GtkTreeModelFilter,但已修改为与早期版本的 Gtk-2.x 兼容,并且具有不同的命名空间,因此不会与 Gtk-2.4 冲突)。

但是,所有这些都有代价:除非您删除所有换行符,否则您不太可能在不到一千行代码的情况下编写一个有用的自定义模型。编写自定义模型并不像看起来那么难,而且可能值得付出努力,尤其是因为它可以让您管理大量数据时得到更清晰的代码。

编写自定义模型需要做些什么?

[编辑 | 编辑源代码]

基本上,您只需要编写一个新的 GObject 来实现 GtkTreeModel 接口,GtkTreeModelIface。您不需要深入了解 GLib GObject 系统——您只需要复制一些样板代码并稍作修改。自定义树模型的核心是您对几个 gtk_tree_model_foo 函数的实现,这些函数揭示了数据的结构,例如,有多少行、一行有多少子节点、有多少列以及它们包含的数据类型。此外,您需要提供将树路径转换为树迭代器以及将树迭代器转换为树路径的函数。此外,您应该提供一些函数来添加和删除自定义模型中的行,但这些函数只供您自己使用,因此不在树模型接口的范围内。

您需要实现的函数是

  • get_flags - 告知外部您的模型具有一些特殊特性,例如持久迭代器。
  • get_n_columns - 每行有多少数据字段对使用 gtk_tree_model_get 的外部可见,例如单元格渲染器属性
  • get_column_type - 对外部可见的数据字段(模型列)中存储的数据类型
  • get_iter - 获取树路径并填充一个迭代器结构,以便您知道它指的是哪一行
  • get_path - 获取一个迭代器并将其转换为树路径,即模型中的“物理”位置
  • get_value - 从一行检索数据
  • iter_next - 获取一个迭代器结构并使其指向下一行
  • iter_children - 告知由给定迭代器表示的行是否有子节点
  • iter_n_children - 告知由给定迭代器表示的行有多少子节点
  • iter_nth_child - 将给定迭代器结构设置为给定父迭代器的第 n 个子节点
  • iter_parent - 将给定迭代器结构设置为给定子迭代器的父节点

由您决定将哪些数据以模型列的形式“显示”给外部,哪些不显示。您可以始终实现特定于自定义模型的函数,这些函数将以您想要的任何形式返回任何数据。您只需要通过 GType 和 GValue 系统将数据“显示”给外部,如果您希望树视图组件访问数据(例如,在设置单元格渲染器属性时)。

示例:一个简单的自定义列表模型

[编辑 | 编辑源代码]

下面是简单自定义列表模型的概要。您可以在下面找到该模型的完整源代码。代码的开头可能看起来有点吓人,但您可以跳过大部分 GObject 和 GType 部分,直接进入自定义列表的核心,即树模型函数的实现。

我们的列表模型由一个简单的记录列表表示,其中每一行对应一个 CustomRecord 结构,该结构跟踪我们感兴趣的数据。现在,我们只想知道姓名和出生年份(通常这不足以证明使用自定义模型,但这只是一个示例)。将模型扩展到处理 CustomRecord 结构中的其他字段非常简单。

在模型中,更准确地说,在 CustomList 结构中,列表以指针数组的形式存储,这不仅提供了对列表中第 n 个记录的快速访问,而且在以后添加排序时也派上用场。除此之外,任何其他特定于列表的数据也应该放在该结构中(例如,活动排序列或用于加速特定行搜索的哈希表等)。

列表中的每一行都由 CustomRecord 结构表示。您可以在该结构中存储任何其他需要的数据。如何提供行数据由您决定。您可以通过树模型接口使用 GValue 系统导出数据,以便您可以使用 gtk_tree_model_get 检索数据,或者您可以提供自定义模型特定的函数来检索数据,例如 custom_list_get_name,它以树迭代器或树路径作为参数。当然,您也可以同时使用这两种方法。

此外,您还需要提供自己的函数来添加行、删除行以及设置或修改行数据,并且您需要通过提供的树模型函数发出相应的信号来让视图和其他对象知道模型中的任何更改。

您应该仔细考虑如何填充模型使用的树迭代器的 GtkTreeIter 字段。您可以使用三个指针字段。这些字段应该填充,以便您可以轻松地根据迭代器识别行,并且还可以方便地访问下一行和父行(如果有)。如果您的模型声明拥有持久迭代器,则您需要确保即使用户将迭代器存储在某处以供以后使用,并且模型被更改或重新排序,迭代器的内容仍然有效。“戳记”字段应该由模型创建时分配给模型的随机模型实例特定整数填充。这样,您可以捕获不属于模型的迭代器。如果您的模型没有持久迭代器,则您应该在模型更改时更改模型的戳记,以便您可以捕获传递给函数的无效迭代器(注意:在下面的代码中,为了节省几行代码,我们不会检查迭代器的戳记)。

在我们的特定示例中,我们只是在模型的树迭代器中存储指向行的 CustomRecord 结构的指针,只要行存在,该指针就有效。此外,我们还在 CustomRecord 中存储了行在列表中的位置,这不仅直观,而且在以后对列表进行排序时也很有用。

如果您想在迭代器的字段中存储整数值,则应该使用 GLib 的 GINT_TO_POINTER 和 GPOINTER_TO_INT 宏。

让我们更详细地看一下代码部分

custom-list.h

[编辑 | 编辑源代码]

自定义列表模型的头文件定义了一些标准类型转换和类型检查宏、我们的 CustomRecord 结构、我们的 CustomList 结构以及一些枚举,用于我们导出的模型列。

CustomRecord 结构代表一行,而 CustomList 结构包含所有列表特定数据。您可以毫无问题地向这两个结构添加额外的字段。例如,您可能需要一个函数,该函数可以根据姓名或出生年份快速查找行,为此,额外的哈希表或类似的东西可能会有用(当然,您需要在插入、修改或删除行时更新这些表)。

您必须导出的唯一函数是 custom_list_get_type,因为它被头文件中定义的类型检查和类型转换宏使用。此外,我们希望导出一个函数来创建我们自定义模型的一个实例,以及一个添加一些行的函数。当您扩展自定义模型以满足您的需求时,您可能会添加更多特定于自定义模型的函数来修改模型。

custom-list.c

[edit | edit source]

首先,我们需要一些样板代码来将我们的自定义模型注册到 GObject 类型系统。您可以跳过本节,直接进入树模型的实现。

本节中感兴趣的函数是 custom_list_init 和 custom_list_get_type。在 custom_list_init 中,我们定义了导出的模型列的数据类型,以及我们导出的列数。在 custom_list_get_type 的末尾,我们将 GtkTreeModel 接口注册到我们的自定义模型对象。在这里,我们还可以注册我们想要实现的其他接口(例如,GtkTreeSortable 或其中一个拖放接口)。

在 custom_list_tree_model_init 中,我们用我们自己的函数覆盖了我们需要实现的那些树模型函数。如果您的模型知道哪些行当前显示在树视图中(例如用于缓存)对您的模型有利,那么您可能还想覆盖 ref_node 和 unref_node 函数。

让我们看一下对象类型注册的核心

GType
custom_list_get_type (void)
{
  static GType custom_list_type = 0;

  /* Some boilerplate type registration stuff */
  if (custom_list_type == 0)
  {
    static const GTypeInfo custom_list_info =
    {
      sizeof (CustomListClass),
      NULL,                                         /* base_init */
      NULL,                                         /* base_finalize */
      (GClassInitFunc) custom_list_class_init,
      NULL,                                         /* class finalize */
      NULL,                                         /* class_data */
      sizeof (CustomList),
      0,                                           /* n_preallocs */
      (GInstanceInitFunc) custom_list_init
    };
    static const GInterfaceInfo tree_model_info =
    {
      (GInterfaceInitFunc) custom_list_tree_model_init,
      NULL,
      NULL
    };

    /* First register our new derived type with the GObject type system */
    custom_list_type = g_type_register_static (G_TYPE_OBJECT, "CustomList",
                                               &custom_list_info, (GTypeFlags)0);

    /* Then register our GtkTreeModel interface with the type system */
    g_type_add_interface_static (custom_list_type, GTK_TYPE_TREE_MODEL, &tree_model_info);
  }

  return custom_list_type;
}

在这里,如果我们已经注册了我们的自定义列表,我们只需返回分配给它的类型。如果没有,我们将注册它并保存类型。在传递给类型系统的三个回调中,只有两个与我们直接相关,分别是 custom_list_tree_model_init 和 custom_list_init。

在 custom_list_tree_model_init 中,我们用指向我们自己函数(至少是我们实现的函数)的指针填充树模型接口结构

static void
custom_list_tree_model_init (GtkTreeModelIface *iface)
{
  /* Here we override the GtkTreeModel
   *  interface functions that we implement */
  iface->get_flags       = custom_list_get_flags;
  iface->get_n_columns   = custom_list_get_n_columns;
  iface->get_column_type = custom_list_get_column_type;
  iface->get_iter        = custom_list_get_iter;
  iface->get_path        = custom_list_get_path;
  iface->get_value       = custom_list_get_value;
  iface->iter_next       = custom_list_iter_next;
  iface->iter_children   = custom_list_iter_children;
  iface->iter_has_child  = custom_list_iter_has_child;
  iface->iter_n_children = custom_list_iter_n_children;
  iface->iter_nth_child  = custom_list_iter_nth_child;
  iface->iter_parent     = custom_list_iter_parent;
}

在 custom_list_init 中,我们将自定义列表结构初始化为合理的默认值。每当创建我们自定义列表的新实例时,此函数都会被调用,我们将在 custom_list_new 中进行此操作。

custom_list_finalize 在我们其中一个列表即将被销毁之前调用。您应该释放您在那里动态分配的所有资源。

处理完所有类型系统相关内容后,我们现在来了解自定义模型的核心,即树模型的实现。我们的树模型函数需要完全按照 API 参考要求的方式运行,包括所有特殊情况,否则它们将无法正常工作。以下是我们正在实现的函数的 API 参考描述的链接列表

  • gtk_tree_model_get_flags
  • gtk_tree_model_get_n_columns
  • gtk_tree_model_get_column_type
  • gtk_tree_model_get_iter
  • gtk_tree_model_get_path
  • gtk_tree_model_get_value
  • gtk_tree_model_iter_next
  • gtk_tree_model_iter_children
  • gtk_tree_model_iter_has_child
  • gtk_tree_model_iter_n_children
  • gtk_tree_model_iter_nth_child
  • gtk_tree_model_iter_parent

几乎所有函数都与 API 参考描述相结合,或多或少地是直接的和自解释的,因此您应该能够直接进入代码并查看它是如何工作的。

在树模型实现之后,我们有那些特定于我们自定义模型的函数。custom_list_new 将为我们创建一个新的自定义列表,custom_list_append_record 将在列表末尾追加一个新记录。请注意,在追加函数的末尾调用 gtk_tree_model_row_inserted,它会在模型上发出“row-inserted”信号,并通知所有感兴趣的对象(树视图、树行引用)已插入新行以及它是在哪里插入的。

每当发生变化时,您需要发出树模型信号,例如,行被插入、删除或重新排序,或者当行从无子行行变为有子行行,或者当行的​​数据发生变化时。以下是您需要在这些情况下使用的函数(我们这里只实现了行插入 - 其他情况留作练习,由读者完成)

  • gtk_tree_model_row_inserted
  • gtk_tree_model_row_changed(使树视图重新绘制该行)
  • gtk_tree_model_row_has_child_toggled
  • gtk_tree_model_row_deleted
  • gtk_tree_model_rows_reordered(注意 bug 124790)

这就是编写自定义模型所需要做的全部事情。

从列表到树

[edit | edit source]

为树编写自定义模型比为简单列表模型编写自定义模型更棘手,但遵循相同的模式。基本上,您只需要扩展上面的模型以适应子项的情况。您可以通过在 CustomList 结构中跟踪整个树层次结构来做到这一点,例如使用 GLib N 元树,或者您可以通过在每个行的 CustomRecord 结构中跟踪每个行的子项来做到这一点,在 CustomList 结构中仅保留指向(不可见)根记录的指针。

TODO:我们这里还需要其他东西吗?

其他接口,这里:GtkTreeSortable 接口

[edit | edit source]

自定义模型可以实现额外的接口来扩展其功能。其他接口有

  • GtkTreeSortableIface
  • GtkTreeDragDestIface
  • GtkTreeDragSourceIface

在这里,我们将以 GtkTreeSortable 接口为例展示如何实现其他接口,我们将仅部分实现它(足以使其正常工作并有用)。

添加另一个接口需要三件事:我们需要在 custom_list_get_type 中将该接口注册到我们的模型,提供一个接口初始化函数,在该函数中我们将接口设置为我们对接口函数的实现,然后提供这些函数的实现。

首先,我们需要在文件开头提供我们函数的函数原型

  /* custom-list.c */

  ...

  /* -- GtkTreeSortable interface functions -- */

  static gboolean     custom_list_sortable_get_sort_column_id (GtkTreeSortable *sortable,
                                                               gint            *sort_col_id,
                                                               GtkSortType     *order);

  static void         custom_list_sortable_set_sort_column_id (GtkTreeSortable *sortable,
                                                               gint             sort_col_id,
                                                               GtkSortType      order);

  static void         custom_list_sortable_set_sort_func (GtkTreeSortable        *sortable,
                                                          gint                    sort_col_id,
                                                          GtkTreeIterCompareFunc  sort_func,
                                                          gpointer                user_data,
                                                          GtkDestroyNotify        destroy_func);

  static void         custom_list_sortable_set_default_sort_func (GtkTreeSortable        *sortable,
                                                                  GtkTreeIterCompareFunc  sort_func,
                                                                  gpointer                user_data,
                                                                  GtkDestroyNotify        destroy_func);

  static gboolean     custom_list_sortable_has_default_sort_func (GtkTreeSortable *sortable);

  static void         custom_list_resort (CustomList *custom_list);

  ...

接下来,让我们扩展 CustomList 结构,添加一个字段用于当前活动的排序列 ID 和一个用于排序顺序的字段,并添加一个用于排序列 ID 的枚举

  /* custom-list.h */

  enum
  {
    SORT_ID_NONE = 0,
    SORT_ID_NAME,
    SORT_ID_YEAR_BORN,
  };

  ...

  struct _CustomList
  {
    GObject         parent;

    guint           num_rows;    /* number of rows that we have */
    CustomRecord  **rows;        /* a dynamically allocated array of pointers to the
                                  *  CustomRecord structure for each row */

    gint            n_columns;
    GType           column_types[CUSTOM_LIST_N_COLUMNS];

    gint            sort_id;
    GtkSortType     sort_order;

    gint            stamp;       /* Random integer to check whether an iter belongs to our model */
  };

  ...

现在,我们确保在 custom_list_new 中初始化新字段,并添加我们的新接口

  ...

  static void    custom_list_sortable_init (GtkTreeSortableIface *iface);

  ...

  void
  custom_list_init (CustomList *custom_list)
  {
    ...
    custom_list->sort_id    = SORT_ID_NONE;
    custom_list->sort_order = GTK_SORT_ASCENDING;
    ...
  }


  GType
  custom_list_get_type (void)
  {
    ...
    /* Add GtkTreeSortable interface */
    {
      static const GInterfaceInfo tree_sortable_info =
      {
        (GInterfaceInitFunc) custom_list_sortable_init,
        NULL,
        NULL
      };

      g_type_add_interface_static (custom_list_type, GTK_TYPE_TREE_SORTABLE, &tree_sortable_info);
    }
    ...
  }


  static void
  custom_list_sortable_init (GtkTreeSortableIface *iface)
  {
    iface->get_sort_column_id    = custom_list_sortable_get_sort_column_id;
    iface->set_sort_column_id    = custom_list_sortable_set_sort_column_id;
    iface->set_sort_func         = custom_list_sortable_set_sort_func;          /* NOT SUPPORTED */
    iface->set_default_sort_func = custom_list_sortable_set_default_sort_func;  /* NOT SUPPORTED */
    iface->has_default_sort_func = custom_list_sortable_has_default_sort_func;  /* NOT SUPPORTED */
  }


Now that we have finally taken care of the administrativa, we implement the tree sortable interface functions:


  static gboolean
  custom_list_sortable_get_sort_column_id (GtkTreeSortable *sortable,
                                           gint            *sort_col_id,
                                           GtkSortType     *order)
  {
    CustomList *custom_list;

    g_return_val_if_fail ( sortable != NULL        , FALSE );
    g_return_val_if_fail ( CUSTOM_IS_LIST(sortable), FALSE );

    custom_list = CUSTOM_LIST(sortable);

    if (sort_col_id)
      *sort_col_id = custom_list->sort_id;

    if (order)
      *order =  custom_list->sort_order;

    return TRUE;
  }


  static void
  custom_list_sortable_set_sort_column_id (GtkTreeSortable *sortable,
                                           gint             sort_col_id,
                                           GtkSortType      order)
  {
    CustomList *custom_list;

    g_return_if_fail ( sortable != NULL         );
    g_return_if_fail ( CUSTOM_IS_LIST(sortable) );

    custom_list = CUSTOM_LIST(sortable);

    if (custom_list->sort_id == sort_col_id && custom_list->sort_order == order)
      return;

    custom_list->sort_id = sort_col_id;
    custom_list->sort_order  = order;

    custom_list_resort(custom_list);

    /* emit "sort-column-changed" signal to tell any tree views
     *  that the sort column has changed (so the little arrow
     *  in the column header of the sort column is drawn
     *  in the right column)                                     */

    gtk_tree_sortable_sort_column_changed(sortable);
  }


  static void
  custom_list_sortable_set_sort_func (GtkTreeSortable        *sortable,
                                      gint                    sort_col_id,
                                      GtkTreeIterCompareFunc  sort_func,
                                      gpointer                user_data,
                                      GtkDestroyNotify        destroy_func)
  {
    g_warning ("%s is not supported by the CustomList model.\n", __FUNCTION__);
  }


  static void
  custom_list_sortable_set_default_sort_func (GtkTreeSortable        *sortable,
                                              GtkTreeIterCompareFunc  sort_func,
                                              gpointer                user_data,
                                              GtkDestroyNotify        destroy_func)
  {
    g_warning ("%s is not supported by the CustomList model.\n", __FUNCTION__);
  }


  static gboolean
  custom_list_sortable_has_default_sort_func (GtkTreeSortable *sortable)
  {
    return FALSE;
  }

现在,最后但并非最不重要的一点是,唯一缺少的是执行实际排序的函数。我们没有实现 set_sort_func、set_default_sort_func 和 set_has_default_sort_func,因为我们在这里使用我们自己的内部排序函数。

实际排序使用 GLib 的 g_qsort_with_data 函数完成,该函数使用快速排序算法对数组进行排序。请注意,我们如何通过在树模型上发出“rows-reordered”信号来通知树视图和其他对象新的行顺序。

  static gint
  custom_list_compare_records (gint sort_id, CustomRecord *a, CustomRecord *b)
  {
    switch(sort_id)
    {
      case SORT_ID_NONE:
        return 0;

      case SORT_ID_NAME:
      {
        if ((a->name) && (b->name))
          return g_utf8_collate(a->name, b->name);

        if (a->name == b->name)
          return 0; /* both are NULL */
        else
          return (a->name == NULL) ? -1 : 1;
      }

      case SORT_ID_YEAR_BORN:
      {
        if (a->year_born == b->year_born)
          return 0;

        return (a->year_born > b->year_born) ? 1 : -1;
      }
    }

    g_return_val_if_reached(0);
  }


  static gint
  custom_list_qsort_compare_func (CustomRecord **a, CustomRecord **b, CustomList *custom_list)
  {
    gint ret;

    g_assert ((a) && (b) && (custom_list));

    ret = custom_list_compare_records(custom_list->sort_id, *a, *b);

    /* Swap -1 and 1 if sort order is reverse */
    if (ret != 0  &&  custom_list->sort_order == GTK_SORT_DESCENDING)
      ret = (ret < 0) ? 1 : -1;

    return ret;
  }


  static void
  custom_list_resort (CustomList *custom_list)
  {
    GtkTreePath *path;
    gint        *neworder, i;

    g_return_if_fail ( custom_list != NULL );
    g_return_if_fail ( CUSTOM_IS_LIST(custom_list) );

    if (custom_list->sort_id == SORT_ID_NONE)
      return;

    if (custom_list->num_rows == 0)
      return;

    /* resort */
    g_qsort_with_data(custom_list->rows,
                      custom_list->num_rows,
                      sizeof(CustomRecord*),
                      (GCompareDataFunc) custom_list_qsort_compare_func,
                      custom_list);

    /* let other objects know about the new order */
    neworder = g_new0(gint, custom_list->num_rows);

    for (i = 0; i < custom_list->num_rows; ++i)
    {
      /* Note that the API reference might be wrong about
       * this, see bug number 124790 on bugs.gnome.org.
       * Both will work, but one will give you 'jumpy'
       * selections after row reordering. */
      /* neworder[(custom_list->rows[i])->pos] = i; */
      neworder[i] = (custom_list->rows[i])->pos;
      (custom_list->rows[i])->pos = i;
    }

    path = gtk_tree_path_new();

    gtk_tree_model_rows_reordered(GTK_TREE_MODEL(custom_list), path, NULL, neworder);

    gtk_tree_path_free(path);
    g_free(neworder);
  }


最后,我们应该确保在插入新行后对模型进行重新排序,方法是在 custom_list_append 的末尾添加对 custom_list_resort 的调用

  ...
  void
  custom_list_append_record (CustomList *custom_list, const gchar *name, guint year_born)
  {
    ...

    custom_list_resort(custom_list);
  }


就是这样。在 main.c 中添加两个对 gtk_tree_view_column_set_sort_column_id 的调用留作另一个练习,由读者完成。

如果您有兴趣查看字符串排序速度问题,您应该修改 main.c,如下所示

  GtkWidget *
  create_view_and_model (void)
  {
    gint i;
    ...
    for (i=0; i < 1000; ++i)
    {
      fill_model(customlist);
    }
    ...
  }

最有可能的是,按姓名对 24000 行进行排序现在将花费几秒钟。现在,如果您返回到 custom_list_compare_records 并用以下内容替换对 g_utf8_collate 的调用

  static gint
  custom_list_compare_records (gint sort_id, CustomRecord *a, CustomRecord *b)
  {
    ...

    if ((a->name) && (b->name))
      return strcmp(a->name_collate_key,b->name_collate_key);

    ...
  }

... 那么您应该有望在按姓名排序时注册显着的速度提升。

工作示例:自定义列表模型源代码

[edit | edit source]

以下是上面介绍的自定义列表模型的完整源代码。使用以下命令编译

 gcc -o customlist custom-list.c main.c `pkg-config --cflags --libs gtk+-2.0`
   *
     custom-list.h
   *
     custom-list.c
   *
     main.c

custom-list.h

[edit | edit source]
#ifndef _custom_list_h_included_
#define _custom_list_h_included_

#include <gtk/gtk.h>

/* Some boilerplate GObject defines. 'klass' is used
 *   instead of 'class', because 'class' is a C++ keyword */

#define CUSTOM_TYPE_LIST            (custom_list_get_type ())
#define CUSTOM_LIST(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), CUSTOM_TYPE_LIST, CustomList))
#define CUSTOM_LIST_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  CUSTOM_TYPE_LIST, CustomListClass))
#define CUSTOM_IS_LIST(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CUSTOM_TYPE_LIST))
#define CUSTOM_IS_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  CUSTOM_TYPE_LIST))
#define CUSTOM_LIST_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  CUSTOM_TYPE_LIST, CustomListClass))

/* The data columns that we export via the tree model interface */

enum
{
  CUSTOM_LIST_COL_RECORD = 0,
  CUSTOM_LIST_COL_NAME,
  CUSTOM_LIST_COL_YEAR_BORN,
  CUSTOM_LIST_N_COLUMNS,
} ;


typedef struct _CustomRecord     CustomRecord;
typedef struct _CustomList       CustomList;
typedef struct _CustomListClass  CustomListClass;



/* CustomRecord: this structure represents a row */

struct _CustomRecord
{
  /* data - you can extend this */
  gchar    *name;
  gchar    *name_collate_key;
  guint     year_born;

  /* admin stuff used by the custom list model */
  guint     pos;   /* pos within the array */
};



/* CustomList: this structure contains everything we need for our
 *             model implementation. You can add extra fields to
 *             this structure, e.g. hashtables to quickly lookup
 *             rows or whatever else you might need, but it is
 *             crucial that 'parent' is the first member of the
 *             structure.                                          */

struct _CustomList
{
  GObject         parent;      /* this MUST be the first member */

  guint           num_rows;    /* number of rows that we have   */
  CustomRecord  **rows;        /* a dynamically allocated array of pointers to
                                *   the CustomRecord structure for each row    */

  /* These two fields are not absolutely necessary, but they    */
  /*   speed things up a bit in our get_value implementation    */
  gint            n_columns;
  GType           column_types[CUSTOM_LIST_N_COLUMNS];

  gint            stamp;       /* Random integer to check whether an iter belongs to our model */
};



/* CustomListClass: more boilerplate GObject stuff */

struct _CustomListClass
{
  GObjectClass parent_class;
};


GType             custom_list_get_type (void);

CustomList       *custom_list_new (void);

void              custom_list_append_record (CustomList   *custom_list,
                                             const gchar  *name,
                                             guint         year_born);

#endif /* _custom_list_h_included_ */
  • custom-list.h
  • custom-list.c
  • main.c

custom-list.c

[edit | edit source]
#include "custom-list.h"


/* boring declarations of local functions */

static void         custom_list_init            (CustomList      *pkg_tree);

static void         custom_list_class_init      (CustomListClass *klass);

static void         custom_list_tree_model_init (GtkTreeModelIface *iface);

static void         custom_list_finalize        (GObject           *object);

static GtkTreeModelFlags custom_list_get_flags  (GtkTreeModel      *tree_model);

static gint         custom_list_get_n_columns   (GtkTreeModel      *tree_model);

static GType        custom_list_get_column_type (GtkTreeModel      *tree_model,
                                                 gint               index);

static gboolean     custom_list_get_iter        (GtkTreeModel      *tree_model,
                                                 GtkTreeIter       *iter,
                                                 GtkTreePath       *path);

static GtkTreePath *custom_list_get_path        (GtkTreeModel      *tree_model,
                                                 GtkTreeIter       *iter);

static void         custom_list_get_value       (GtkTreeModel      *tree_model,
                                                 GtkTreeIter       *iter,
                                                 gint               column,
                                                 GValue            *value);

static gboolean     custom_list_iter_next       (GtkTreeModel      *tree_model,
                                                 GtkTreeIter       *iter);

static gboolean     custom_list_iter_children   (GtkTreeModel      *tree_model,
                                                 GtkTreeIter       *iter,
                                                 GtkTreeIter       *parent);

static gboolean     custom_list_iter_has_child  (GtkTreeModel      *tree_model,
                                                 GtkTreeIter       *iter);

static gint         custom_list_iter_n_children (GtkTreeModel      *tree_model,
                                                 GtkTreeIter       *iter);

static gboolean     custom_list_iter_nth_child  (GtkTreeModel      *tree_model,
                                                 GtkTreeIter       *iter,
                                                 GtkTreeIter       *parent,
                                                 gint               n);

static gboolean     custom_list_iter_parent     (GtkTreeModel      *tree_model,
                                                 GtkTreeIter       *iter,
                                                 GtkTreeIter       *child);



static GObjectClass *parent_class = NULL;  /* GObject stuff - nothing to worry about */


/*****************************************************************************
 *
 *  custom_list_get_type: here we register our new type and its interfaces
 *                        with the type system. If you want to implement
 *                        additional interfaces like GtkTreeSortable, you
 *                        will need to do it here.
 *
 *****************************************************************************/

GType
custom_list_get_type (void)
{
  static GType custom_list_type = 0;

  /* Some boilerplate type registration stuff */
  if (custom_list_type == 0)
  {
    static const GTypeInfo custom_list_info =
    {
      sizeof (CustomListClass),
      NULL,                                         /* base_init */
      NULL,                                         /* base_finalize */
      (GClassInitFunc) custom_list_class_init,
      NULL,                                         /* class finalize */
      NULL,                                         /* class_data */
      sizeof (CustomList),
      0,                                           /* n_preallocs */
      (GInstanceInitFunc) custom_list_init
    };
    static const GInterfaceInfo tree_model_info =
    {
      (GInterfaceInitFunc) custom_list_tree_model_init,
      NULL,
      NULL
    };

    /* First register the new derived type with the GObject type system */
    custom_list_type = g_type_register_static (G_TYPE_OBJECT, "CustomList",
                                               &custom_list_info, (GTypeFlags)0);

    /* Now register our GtkTreeModel interface with the type system */
    g_type_add_interface_static (custom_list_type, GTK_TYPE_TREE_MODEL, &tree_model_info);
  }

  return custom_list_type;
}


/*****************************************************************************
 *
 *  custom_list_class_init: more boilerplate GObject/GType stuff.
 *                          Init callback for the type system,
 *                          called once when our new class is created.
 *
 *****************************************************************************/

static void
custom_list_class_init (CustomListClass *klass)
{
  GObjectClass *object_class;

  parent_class = (GObjectClass*) g_type_class_peek_parent (klass);
  object_class = (GObjectClass*) klass;

  object_class->finalize = custom_list_finalize;
}

/*****************************************************************************
 *
 *  custom_list_tree_model_init: init callback for the interface registration
 *                               in custom_list_get_type. Here we override
 *                               the GtkTreeModel interface functions that
 *                               we implement.
 *
 *****************************************************************************/

static void
custom_list_tree_model_init (GtkTreeModelIface *iface)
{
  iface->get_flags       = custom_list_get_flags;
  iface->get_n_columns   = custom_list_get_n_columns;
  iface->get_column_type = custom_list_get_column_type;
  iface->get_iter        = custom_list_get_iter;
  iface->get_path        = custom_list_get_path;
  iface->get_value       = custom_list_get_value;
  iface->iter_next       = custom_list_iter_next;
  iface->iter_children   = custom_list_iter_children;
  iface->iter_has_child  = custom_list_iter_has_child;
  iface->iter_n_children = custom_list_iter_n_children;
  iface->iter_nth_child  = custom_list_iter_nth_child;
  iface->iter_parent     = custom_list_iter_parent;
}


/*****************************************************************************
 *
 *  custom_list_init: this is called everytime a new custom list object
 *                    instance is created (we do that in custom_list_new).
 *                    Initialise the list structure's fields here.
 *
 *****************************************************************************/

static void
custom_list_init (CustomList *custom_list)
{
  custom_list->n_columns       = CUSTOM_LIST_N_COLUMNS;

  custom_list->column_types[0] = G_TYPE_POINTER;  /* CUSTOM_LIST_COL_RECORD    */
  custom_list->column_types[1] = G_TYPE_STRING;   /* CUSTOM_LIST_COL_NAME      */
  custom_list->column_types[2] = G_TYPE_UINT;     /* CUSTOM_LIST_COL_YEAR_BORN */

  g_assert (CUSTOM_LIST_N_COLUMNS == 3);

  custom_list->num_rows = 0;
  custom_list->rows     = NULL;

  custom_list->stamp = g_random_int();  /* Random int to check whether an iter belongs to our model */

}


/*****************************************************************************
 *
 *  custom_list_finalize: this is called just before a custom list is
 *                        destroyed. Free dynamically allocated memory here.
 *
 *****************************************************************************/

static void
custom_list_finalize (GObject *object)
{
/*  CustomList *custom_list = CUSTOM_LIST(object); */

  /* free all records and free all memory used by the list */
  #warning IMPLEMENT

  /* must chain up - finalize parent */
  (* parent_class->finalize) (object);
}


/*****************************************************************************
 *
 *  custom_list_get_flags: tells the rest of the world whether our tree model
 *                         has any special characteristics. In our case,
 *                         we have a list model (instead of a tree), and each
 *                         tree iter is valid as long as the row in question
 *                         exists, as it only contains a pointer to our struct.
 *
 *****************************************************************************/

static GtkTreeModelFlags
custom_list_get_flags (GtkTreeModel *tree_model)
{
  g_return_val_if_fail (CUSTOM_IS_LIST(tree_model), (GtkTreeModelFlags)0);

  return (GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST);
}


/*****************************************************************************
 *
 *  custom_list_get_n_columns: tells the rest of the world how many data
 *                             columns we export via the tree model interface
 *
 *****************************************************************************/

static gint
custom_list_get_n_columns (GtkTreeModel *tree_model)
{
  g_return_val_if_fail (CUSTOM_IS_LIST(tree_model), 0);

  return CUSTOM_LIST(tree_model)->n_columns;
}


/*****************************************************************************
 *
 *  custom_list_get_column_type: tells the rest of the world which type of
 *                               data an exported model column contains
 *
 *****************************************************************************/

static GType
custom_list_get_column_type (GtkTreeModel *tree_model,
                             gint          index)
{
  g_return_val_if_fail (CUSTOM_IS_LIST(tree_model), G_TYPE_INVALID);
  g_return_val_if_fail (index < CUSTOM_LIST(tree_model)->n_columns && index >= 0, G_TYPE_INVALID);

  return CUSTOM_LIST(tree_model)->column_types[index];
}


/*****************************************************************************
 *
 *  custom_list_get_iter: converts a tree path (physical position) into a
 *                        tree iter structure (the content of the iter
 *                        fields will only be used internally by our model).
 *                        We simply store a pointer to our CustomRecord
 *                        structure that represents that row in the tree iter.
 *
 *****************************************************************************/

static gboolean
custom_list_get_iter (GtkTreeModel *tree_model,
                      GtkTreeIter  *iter,
                      GtkTreePath  *path)
{
  CustomList    *custom_list;
  CustomRecord  *record;
  gint          *indices, n, depth;

  g_assert(CUSTOM_IS_LIST(tree_model));
  g_assert(path!=NULL);

  custom_list = CUSTOM_LIST(tree_model);

  indices = gtk_tree_path_get_indices(path);
  depth   = gtk_tree_path_get_depth(path);

  /* we do not allow children */
  g_assert(depth == 1); /* depth 1 = top level; a list only has top level nodes and no children */

  n = indices[0]; /* the n-th top level row */

  if ( n >= custom_list->num_rows || n < 0 )
    return FALSE;

  record = custom_list->rows[n];

  g_assert(record != NULL);
  g_assert(record->pos == n);

  /* We simply store a pointer to our custom record in the iter */
  iter->stamp      = custom_list->stamp;
  iter->user_data  = record;
  iter->user_data2 = NULL;   /* unused */
  iter->user_data3 = NULL;   /* unused */

  return TRUE;
}


/*****************************************************************************
 *
 *  custom_list_get_path: converts a tree iter into a tree path (ie. the
 *                        physical position of that row in the list).
 *
 *****************************************************************************/

static GtkTreePath *
custom_list_get_path (GtkTreeModel *tree_model,
                      GtkTreeIter  *iter)
{
  GtkTreePath  *path;
  CustomRecord *record;
  CustomList   *custom_list;

  g_return_val_if_fail (CUSTOM_IS_LIST(tree_model), NULL);
  g_return_val_if_fail (iter != NULL,               NULL);
  g_return_val_if_fail (iter->user_data != NULL,    NULL);

  custom_list = CUSTOM_LIST(tree_model);

  record = (CustomRecord*) iter->user_data;

  path = gtk_tree_path_new();
  gtk_tree_path_append_index(path, record->pos);

  return path;
}


/*****************************************************************************
 *
 *  custom_list_get_value: Returns a row's exported data columns
 *                         (_get_value is what gtk_tree_model_get uses)
 *
 *****************************************************************************/

static void
custom_list_get_value (GtkTreeModel *tree_model,
                       GtkTreeIter  *iter,
                       gint          column,
                       GValue       *value)
{
  CustomRecord  *record;
  CustomList    *custom_list;

  g_return_if_fail (CUSTOM_IS_LIST (tree_model));
  g_return_if_fail (iter != NULL);
  g_return_if_fail (column < CUSTOM_LIST(tree_model)->n_columns);

  g_value_init (value, CUSTOM_LIST(tree_model)->column_types[column]);

  custom_list = CUSTOM_LIST(tree_model);

  record = (CustomRecord*) iter->user_data;

  g_return_if_fail ( record != NULL );

  if(record->pos >= custom_list->num_rows)
   g_return_if_reached();

  switch(column)
  {
    case CUSTOM_LIST_COL_RECORD:
      g_value_set_pointer(value, record);
      break;

    case CUSTOM_LIST_COL_NAME:
      g_value_set_string(value, record->name);
      break;

    case CUSTOM_LIST_COL_YEAR_BORN:
      g_value_set_uint(value, record->year_born);
      break;
  }
}


/*****************************************************************************
 *
 *  custom_list_iter_next: Takes an iter structure and sets it to point
 *                         to the next row.
 *
 *****************************************************************************/

static gboolean
custom_list_iter_next (GtkTreeModel  *tree_model,
                       GtkTreeIter   *iter)
{
  CustomRecord  *record, *nextrecord;
  CustomList    *custom_list;

  g_return_val_if_fail (CUSTOM_IS_LIST (tree_model), FALSE);

  if (iter == NULL || iter->user_data == NULL)
    return FALSE;

  custom_list = CUSTOM_LIST(tree_model);

  record = (CustomRecord *) iter->user_data;

  /* Is this the last record in the list? */
  if ((record->pos + 1) >= custom_list->num_rows)
    return FALSE;

  nextrecord = custom_list->rows[(record->pos + 1)];

  g_assert ( nextrecord != NULL );
  g_assert ( nextrecord->pos == (record->pos + 1) );

  iter->stamp     = custom_list->stamp;
  iter->user_data = nextrecord;

  return TRUE;
}


/*****************************************************************************
 *
 *  custom_list_iter_children: Returns TRUE or FALSE depending on whether
 *                             the row specified by 'parent' has any children.
 *                             If it has children, then 'iter' is set to
 *                             point to the first child. Special case: if
 *                             'parent' is NULL, then the first top-level
 *                             row should be returned if it exists.
 *
 *****************************************************************************/

static gboolean
custom_list_iter_children (GtkTreeModel *tree_model,
                           GtkTreeIter  *iter,
                           GtkTreeIter  *parent)
{
  CustomList  *custom_list;

  g_return_val_if_fail (parent == NULL || parent->user_data != NULL, FALSE);

  /* this is a list, nodes have no children */
  if (parent)
    return FALSE;

  /* parent == NULL is a special case; we need to return the first top-level row */

  g_return_val_if_fail (CUSTOM_IS_LIST (tree_model), FALSE);

  custom_list = CUSTOM_LIST(tree_model);

  /* No rows => no first row */
  if (custom_list->num_rows == 0)
    return FALSE;

  /* Set iter to first item in list */
  iter->stamp     = custom_list->stamp;
  iter->user_data = custom_list->rows[0];

  return TRUE;
}


/*****************************************************************************
 *
 *  custom_list_iter_has_child: Returns TRUE or FALSE depending on whether
 *                              the row specified by 'iter' has any children.
 *                              We only have a list and thus no children.
 *
 *****************************************************************************/

static gboolean
custom_list_iter_has_child (GtkTreeModel *tree_model,
                            GtkTreeIter  *iter)
{
  return FALSE;
}


/*****************************************************************************
 *
 *  custom_list_iter_n_children: Returns the number of children the row
 *                               specified by 'iter' has. This is usually 0,
 *                               as we only have a list and thus do not have
 *                               any children to any rows. A special case is
 *                               when 'iter' is NULL, in which case we need
 *                               to return the number of top-level nodes,
 *                               ie. the number of rows in our list.
 *
 *****************************************************************************/

static gint
custom_list_iter_n_children (GtkTreeModel *tree_model,
                             GtkTreeIter  *iter)
{
  CustomList  *custom_list;

  g_return_val_if_fail (CUSTOM_IS_LIST (tree_model), -1);
  g_return_val_if_fail (iter == NULL || iter->user_data != NULL, FALSE);

  custom_list = CUSTOM_LIST(tree_model);

  /* special case: if iter == NULL, return number of top-level rows */
  if (!iter)
    return custom_list->num_rows;

  return 0; /* otherwise, this is easy again for a list */
}


/*****************************************************************************
 *
 *  custom_list_iter_nth_child: If the row specified by 'parent' has any
 *                              children, set 'iter' to the n-th child and
 *                              return TRUE if it exists, otherwise FALSE.
 *                              A special case is when 'parent' is NULL, in
 *                              which case we need to set 'iter' to the n-th
 *                              row if it exists.
 *
 *****************************************************************************/

static gboolean
custom_list_iter_nth_child (GtkTreeModel *tree_model,
                            GtkTreeIter  *iter,
                            GtkTreeIter  *parent,
                            gint          n)
{
  CustomRecord  *record;
  CustomList    *custom_list;

  g_return_val_if_fail (CUSTOM_IS_LIST (tree_model), FALSE);

  custom_list = CUSTOM_LIST(tree_model);

  /* a list has only top-level rows */
  if(parent)
    return FALSE;

  /* special case: if parent == NULL, set iter to n-th top-level row */

  if( n >= custom_list->num_rows )
    return FALSE;

  record = custom_list->rows[n];

  g_assert( record != NULL );
  g_assert( record->pos == n );

  iter->stamp = custom_list->stamp;
  iter->user_data = record;

  return TRUE;
}


/*****************************************************************************
 *
 *  custom_list_iter_parent: Point 'iter' to the parent node of 'child'. As
 *                           we have a list and thus no children and no
 *                           parents of children, we can just return FALSE.
 *
 *****************************************************************************/

static gboolean
custom_list_iter_parent (GtkTreeModel *tree_model,
                         GtkTreeIter  *iter,
                         GtkTreeIter  *child)
{
  return FALSE;
}


/*****************************************************************************
 *
 *  custom_list_new:  This is what you use in your own code to create a
 *                    new custom list tree model for you to use.
 *
 *****************************************************************************/

CustomList *
custom_list_new (void)
{
  CustomList *newcustomlist;

  newcustomlist = (CustomList*) g_object_new (CUSTOM_TYPE_LIST, NULL);

  g_assert( newcustomlist != NULL );

  return newcustomlist;
}


/*****************************************************************************
 *
 *  custom_list_append_record:  Empty lists are boring. This function can
 *                              be used in your own code to add rows to the
 *                              list. Note how we emit the "row-inserted"
 *                              signal after we have appended the row
 *                              internally, so the tree view and other
 *                              interested objects know about the new row.
 *
 *****************************************************************************/

void
custom_list_append_record (CustomList   *custom_list,
                           const gchar  *name,
                           guint         year_born)
{
  GtkTreeIter   iter;
  GtkTreePath  *path;
  CustomRecord *newrecord;
  gulong        newsize;
  guint         pos;

  g_return_if_fail (CUSTOM_IS_LIST(custom_list));
  g_return_if_fail (name != NULL);

  pos = custom_list->num_rows;

  custom_list->num_rows++;

  newsize = custom_list->num_rows * sizeof(CustomRecord*);

  custom_list->rows = g_realloc(custom_list->rows, newsize);

  newrecord = g_new0(CustomRecord, 1);

  newrecord->name = g_strdup(name);
  newrecord->name_collate_key = g_utf8_collate_key(name,-1); /* for fast sorting, used later */
  newrecord->year_born = year_born;

  custom_list->rows[pos] = newrecord;
  newrecord->pos = pos;

  /* inform the tree view and other interested objects
   *  (e.g. tree row references) that we have inserted
   *  a new row, and where it was inserted */

  path = gtk_tree_path_new();
  gtk_tree_path_append_index(path, newrecord->pos);

  custom_list_get_iter(GTK_TREE_MODEL(custom_list), &iter, path);

  gtk_tree_model_row_inserted(GTK_TREE_MODEL(custom_list), path, &iter);

  gtk_tree_path_free(path);
}
  • custom-list.h
  • custom-list.c
  • main.c


main.c

[edit | edit source]

以下几行提供了一个工作测试用例,该用例使用了我们的自定义列表。它创建了我们自定义列表之一,添加了一些记录,并在树视图中显示它。

#include "custom-list.h"
#include <stdlib.h>

void
fill_model (CustomList *customlist)
{
  const gchar  *firstnames[] = { "Joe", "Jane", "William", "Hannibal", "Timothy", "Gargamel", NULL } ;
  const gchar  *surnames[]   = { "Grokowich", "Twitch", "Borheimer", "Bork", NULL } ;
  const gchar **fname, **sname;

  for (sname = surnames;  *sname != NULL;  sname++)
  {
    for (fname = firstnames;  *fname != NULL;  fname++)
    {
      gchar *name = g_strdup_printf ("%s %s", *fname, *sname);

      custom_list_append_record (customlist, name, 1900 + (guint) (103.0*rand()/(RAND_MAX+1900.0)));

      g_free(name);
    }
  }
}

GtkWidget *
create_view_and_model (void)
{
  GtkTreeViewColumn   *col;
  GtkCellRenderer     *renderer;
  CustomList          *customlist;
  GtkWidget           *view;

  customlist = custom_list_new();
  fill_model(customlist);

  view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(customlist));

  g_object_unref(customlist); /* destroy store automatically with view */

  renderer = gtk_cell_renderer_text_new();
  col = gtk_tree_view_column_new();

  gtk_tree_view_column_pack_start (col, renderer, TRUE);
  gtk_tree_view_column_add_attribute (col, renderer, "text", CUSTOM_LIST_COL_NAME);
  gtk_tree_view_column_set_title (col, "Name");
  gtk_tree_view_append_column(GTK_TREE_VIEW(view),col);

  renderer = gtk_cell_renderer_text_new();
  col = gtk_tree_view_column_new();
  gtk_tree_view_column_pack_start (col, renderer, TRUE);
  gtk_tree_view_column_add_attribute (col, renderer, "text", CUSTOM_LIST_COL_YEAR_BORN);
  gtk_tree_view_column_set_title (col, "Year Born");
  gtk_tree_view_append_column(GTK_TREE_VIEW(view),col);

  return view;
}

int
main (int argc, char **argv)
{
  GtkWidget *window, *view, *scrollwin;

  gtk_init(&argc,&argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_default_size (GTK_WINDOW(window), 200, 400);
  g_signal_connect(window, "delete_event", gtk_main_quit, NULL);

  scrollwin = gtk_scrolled_window_new(NULL,NULL);

  view = create_view_and_model();

  gtk_container_add(GTK_CONTAINER(scrollwin), view);
  gtk_container_add(GTK_CONTAINER(window), scrollwin);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}
华夏公益教科书