跳转到内容

GTK+ 例子/树视图/列和渲染器

来自维基教科书,开放世界中的开放书籍

将数据映射到屏幕:GtkTreeViewColumn 和 GtkCellRenderer

[编辑 | 编辑源代码]

如上所述,树视图列表示屏幕上可见的列,这些列具有一个带有列名的列标题,并且可以调整大小或排序。树视图由树视图列组成,为了在树视图中显示某些内容,您至少需要一个树视图列。但是,树视图列本身不显示任何内容,这是由专门的 GtkCellRenderer 对象完成的。单元格渲染器与小部件打包到 GtkHBoxes 中的方式一样打包到树视图列中。

以下是(由 Owen Taylor 提供)一个描述树视图列和单元格渲染器之间关系的图表

图 5-1. 单元格渲染器属性

在上图中,“国家”和“代表”都是树视图列,其中“国家”和“代表”标签是列标题。“国家”列包含两个单元格渲染器,一个用于显示国旗图标,另一个用于显示国家名称。“代表”列仅包含一个单元格渲染器,用于显示代表的姓名。

单元格渲染器

[编辑 | 编辑源代码]

单元格渲染器是负责在 GtkTreeViewColumn 中实际渲染数据的对象。它们基本上只是具有某些属性的 GObjects(即不是小部件),这些属性决定了单个单元格的绘制方式。

为了在具有不同内容的不同行中绘制单元格,需要相应地设置单元格渲染器的属性,以便渲染每个单独的行/单元格。这可以通过属性或单元格数据函数(见下文)来完成。如果您设置属性,则告诉 Gtk 哪个模型列包含用于在渲染特定行之前设置属性的数据。然后,单元格渲染器的属性会根据模型中每个渲染行之前的数据自动设置。或者,您可以设置单元格数据函数,这些函数将在渲染每个行时调用,以便您可以在渲染之前手动设置单元格渲染器的属性。这两种方法也可以同时使用。最后,您可以在创建单元格渲染器时设置单元格渲染器属性。这样,它将用于渲染所有行/单元格(除非稍后更改)。

不同的单元格渲染器用于不同的目的

  • GtkCellRendererText 将字符串、数字或布尔值作为文本渲染(“Joe”、“99.32”、“true”)。
  • GtkCellRendererPixbuf 用于显示图像;无论是用户定义的图像,还是与 Gtk+ 捆绑在一起的库存图标之一。
  • GtkCellRendererToggle 以复选框或单选按钮的形式显示布尔值。
  • GtkCellEditable 是一个特殊的单元格,它实现可编辑的单元格(即树视图中的 GtkEntry 或 GtkSpinbutton)。这不是一个单元格渲染器!如果您想要具有可编辑的文本单元格,请使用 GtkCellRendererText 并确保设置了“可编辑”属性。GtkCellEditable 仅由可编辑单元格的实现以及可以位于可编辑单元格内部的小部件使用。您不太可能需要它。

与人们的直觉相反,单元格渲染器不仅渲染单个单元格,而且负责为每一行渲染树视图列的一部分或全部。它基本上从第一行开始,并在那里渲染其列的一部分。然后它继续到下一行,并在那里再次渲染其列的一部分。等等。

单元格渲染器如何知道要渲染什么?单元格渲染器对象具有一些“属性”,这些属性在 API 参考中进行了记录(就像大多数其他对象和小部件一样)。这些属性决定了单元格渲染器将要渲染的内容以及渲染方式。每当调用单元格渲染器来渲染某个单元格时,它都会查看其属性并相应地渲染单元格。这意味着,每当您设置或更改单元格渲染器的属性时,这将影响更改后渲染的所有行,直到您再次更改该属性。

以下是(由 Owen Taylor 提供)一个试图展示渲染行时发生情况的图表

图 5-2. GtkTreeViewColumns 和 GtkCellRenderers

上图展示了使用属性时的过程。在本例中,文本单元格渲染器的“text”属性已链接到第一个模型列。“text”属性包含要渲染的字符串。“foreground”属性包含要显示的文本颜色,已链接到第二个模型列。最后,“strikethrough”属性决定文本是否应使用穿过文本的水平线,已连接到第三个模型列(类型为 G_TYPE_BOOLEAN)。

通过这种设置,在渲染每个单元格之前,单元格渲染器的属性会从模型中“加载”。

以下是一个愚蠢且毫无用处的简单示例,它演示了这种行为,并介绍了一些最常用的 GtkCellRendererText 属性

#include <gtk/gtk.h>

enum
{
  COL_FIRST_NAME = 0,
  COL_LAST_NAME,
  NUM_COLS
} ;

static GtkTreeModel *
create_and_fill_model (void)
{
  GtkTreeStore  *treestore;
  GtkTreeIter    toplevel, child;

  treestore = gtk_tree_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_STRING);

  /* Append a top level row and leave it empty */
  gtk_tree_store_append(treestore, &toplevel, NULL);

  /* Append a second top level row, and fill it with some data */
  gtk_tree_store_append(treestore, &toplevel, NULL);
  gtk_tree_store_set(treestore, &toplevel,
                     COL_FIRST_NAME, "Joe",
                     COL_LAST_NAME, "Average",
                     -1);

  /* Append a child to the second top level row, and fill in some data */
  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child,
                     COL_FIRST_NAME, "Jane",
                     COL_LAST_NAME, "Average",
                     -1);

  return GTK_TREE_MODEL(treestore);
}

static GtkWidget *
create_view_and_model (void)
{
  GtkTreeViewColumn   *col;
  GtkCellRenderer     *renderer;
  GtkWidget           *view;
  GtkTreeModel        *model;

  view = gtk_tree_view_new();

  /* --- Column #1 --- */

  col = gtk_tree_view_column_new();

  gtk_tree_view_column_set_title(col, "First Name");

  /* pack tree view column into tree view */
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);

  renderer = gtk_cell_renderer_text_new();

  /* pack cell renderer into tree view column */
  gtk_tree_view_column_pack_start(col, renderer, TRUE);

  /* set 'text' property of the cell renderer */
  g_object_set(renderer, "text", "Boooo!", NULL);


  /* --- Column #2 --- */

  col = gtk_tree_view_column_new();

  gtk_tree_view_column_set_title(col, "Last Name");

  /* pack tree view column into tree view */
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);

  renderer = gtk_cell_renderer_text_new();

  /* pack cell renderer into tree view column */
  gtk_tree_view_column_pack_start(col, renderer, TRUE);

  /* set 'cell-background' property of the cell renderer */
  g_object_set(renderer,
               "cell-background", "Orange",
               "cell-background-set", TRUE,
               NULL);

  model = create_and_fill_model();

  gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);

  g_object_unref(model); /* destroy model automatically with view */

  gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)),
                              GTK_SELECTION_NONE);

  return view;
}


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

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  g_signal_connect(window, "delete_event", gtk_main_quit, NULL); /* dirty */

  view = create_view_and_model();

  gtk_container_add(GTK_CONTAINER(window), view);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}


以上代码应生成类似于以下内容的结果

图 5-3. 持久单元格渲染器属性

看起来树视图显示部分正确,部分不完整。一方面,树视图渲染了正确的行数(注意第 3 行之后右侧没有橙色),并且正确地显示了层次结构(左侧),但它没有显示我们存储在模型中的任何数据。这是因为我们没有在单元格渲染器应该渲染的内容和模型中的数据之间建立任何连接。我们只是在启动时设置了一些单元格渲染器属性,而单元格渲染器一丝不苟地遵守了这些设置的属性。

有两种不同的方法可以将单元格渲染器连接到模型中的数据:属性和单元格数据函数。

属性是单元格渲染器属性和模型中的字段/列之间的连接。每当要渲染单元格时,单元格渲染器属性将设置为要渲染行的指定模型列的值。重要的是,列的数据类型与 API 参考手册中属性采用的类型相同。以下是一些代码供参考

   ...

   col = gtk_tree_view_column_new();

   renderer = gtk_cell_renderer_text_new();

   gtk_tree_view_column_pack_start(col, renderer, TRUE);

   gtk_tree_view_column_add_attribute(col, renderer, "text", COL_FIRST_NAME);

   ...

这意味着文本单元格渲染器属性“text”将设置为要绘制的每一行的模型列 COL_FIRST_NAME 中的字符串。重要的是要理解 gtk_tree_view_column_add_attribute 和 g_object_set 之间的区别:g_object_set 将属性设置为某个值,而 gtk_tree_view_column_add_attribute 将属性设置为渲染时指定的 _模型列_ 中的值。

同样,在设置属性时,重要的是模型列中存储的数据类型与属性作为参数所需的类型相同。检查 API 参考手册以查看每个属性所需的类型。在阅读上面示例时,您可能已经注意到我们设置了 GtkCellRendererText 的“cell-background”属性,即使 API 文档没有列出此属性。我们可以这样做,因为 GtkCellRendererText 是从 GtkCellRenderer 派生的,而 GtkCellRenderer 确实具有此属性。派生类继承其父类的属性。这与可以转换为其祖先类之一的小部件相同。API 参考具有一个对象层次结构,显示了小部件或其他对象从哪个类派生。

关于 GtkCellRenderer 属性,还有两点值得注意:一是有时存在多个属性执行相同的操作,但接受不同的参数,例如 GtkCellRendererText 的 "foreground" 和 "foreground-gdk" 属性(它们指定文本颜色)。"foreground" 属性接收字符串形式的颜色,例如 "Orange" 或 "CornflowerBlue",而 "foreground-gdk" 则接收 GdkColor 参数。您可以选择使用哪一个 - 它们的效果相同。另一个值得一提的是,大多数属性都有一个 "foo-set" 属性,它接受布尔值作为参数,例如 "foreground-set"。当您希望某个设置生效或失效时,这很有用。如果您设置了 "foreground" 属性,但将 "foreground-set" 设置为 FALSE,那么您的前景颜色设置将被忽略。这在单元格数据函数(见下文)中很有用,例如,如果您想在启动时将前景颜色设置为某个值,但只想在某些列中生效,而不想在其他列中生效(在这种情况下,您可以将 "foreground-set" 属性连接到类型为 G_TYPE_BOOLEAN 的模型列,使用 gtk_tree_view_column_add_attribute)。

设置列属性是最直接的方式,可以将模型数据显示出来。当您希望模型中的数据按原样显示时,通常使用这种方式。

另一种显示模型数据的方法是设置单元格数据函数。

单元格数据函数

[编辑 | 编辑源代码]

单元格数据函数是在渲染每行之前,针对特定单元格渲染器调用的一种函数。它为您提供了对渲染内容的完全控制,您可以像想要的那样设置单元格渲染器的属性。请记住,不仅要设置属性使其生效,还要取消设置属性使其失效(如果该属性可能在上一行被设置)。

如果要更细致地控制显示内容,或者标准的显示方式不完全符合您的需求,则通常使用单元格数据函数。例如,浮点数。如果您希望浮点数以特定方式显示,例如只显示小数点后一位,则需要使用单元格数据函数。使用 gtk_tree_view_column_set_cell_data_func 为特定单元格渲染器设置单元格数据函数。以下是一个示例:

   enum
   {
     COLUMN_NAME = 0,
     COLUMN_AGE_FLOAT,
     NUM_COLS
   };

   ...

   void
   age_cell_data_function (GtkTreeViewColumn *col,
                           GtkCellRenderer   *renderer,
                           GtkTreeModel      *model,
                           GtkTreeIter       *iter,
                           gpointer           user_data)
   {
     gfloat  age;
     gchar   buf[20];

     gtk_tree_model_get(model, iter, COLUMN_AGE_FLOAT, &age, -1);

     g_snprintf(buf, sizeof(buf), "%.1f", age);

     g_object_set(renderer, "text", buf, NULL);
   }

   ...

   liststore = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_FLOAT);

   col = gtk_tree_view_column_new();

   cell = gtk_cell_renderer_text_new();

   gtk_tree_view_column_pack_start(col, cell, TRUE);

   gtk_tree_view_column_set_cell_data_func(col, cell, age_cell_data_func, NULL, NULL);

   ...

对于由特定单元格渲染器渲染的每一行,都会调用单元格数据函数,该函数会从模型中检索浮点数,并将它转换为一个字符串,其中浮点数只保留小数点后一位,然后使用文本单元格渲染器渲染该字符串。

这只是一个简单的例子,您可以根据需要创建更复杂的单元格数据函数。和往常一样,需要权衡利弊。您的单元格数据函数将在渲染该(渲染器)列中的每个单元格时被调用。如果使用单元格数据函数,请检查它在程序中被调用的频率。如果您在单元格数据函数中执行耗时的操作,那么效率将会降低,尤其是当您有大量行时。在这种情况下,另一种方法是创建一个额外的列 COLUMN_AGE_FLOAT_STRING,类型为 G_TYPE_STRING,并在设置行中的浮点数时,将浮点数以字符串形式设置,然后使用属性将字符串列连接到文本单元格渲染器。这样,浮点数到字符串的转换只需执行一次。这是一个 CPU 周期/内存的权衡,具体哪种方法更合适取决于您的具体情况。您可能不应该做的事情是,例如在单元格数据函数中将长字符串转换为 UTF8 格式。

您可能会注意到,即使对于当前不可见的行,您的单元格数据函数也会被调用。这是因为树形视图需要知道其总高度,为了计算总高度,它需要知道每一行的高度,而它只有通过测量才能知道,当您有大量高度不同的行时,测量过程会很慢(如果您的所有行都具有相同的高度,则应该不会有任何明显的延迟)。

GtkCellRendererText 和 Integer、Boolean 和 Float 类型

[编辑 | 编辑源代码]

之前说过,当使用属性将模型中的数据连接到单元格渲染器属性时,在 gtk_tree_view_column_add_attribute 中指定的模型列中的数据必须始终与属性所需的数据类型相同。

这通常是正确的,但有一个例外:如果您使用 gtk_tree_view_column_add_attribute 将文本单元格渲染器的 "text" 属性连接到模型列,则模型列不需要是 G_TYPE_STRING,也可以是大多数其他基本 GLib 类型,例如 G_TYPE_BOOLEAN、G_TYPE_INT、G_TYPE_UINT、G_TYPE_LONG、G_TYPE_ULONG、G_TYPE_INT64、G_TYPE_UINT64、G_TYPE_FLOAT 或 G_TYPE_DOUBLE。文本单元格渲染器会自动在树形视图中正确显示这些类型的 value。例如:

  enum
  {
    COL_NAME = 0,
    COL_YEAR_BORN,
    NUM_COLS
  };

  liststore = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_UINT);

  ...

  cell = gtk_cell_renderer_text_new();
  col = gtk_tree_view_column_new();
  gtk_tree_view_column_add_attribute(col, cell, "text", COL_YEAR_BORN);

  ...

尽管 "text" 属性需要字符串值,但我们在设置属性时使用了整数类型的模型列。然后,整数将被自动转换为字符串,然后再设置单元格渲染器属性 [1]。

如果您使用的是浮点类型,即 G_TYPE_FLOAT 或 G_TYPE_DOUBLE,则无法告诉文本单元格渲染器应该渲染小数点后多少位。如果您只想要小数点后特定数量的位,则需要使用单元格数据函数。注释 [1]

对于那些有兴趣的人来说,转换实际上是在 g_object_set_property 中进行的。在渲染某个单元格之前,树形视图列会调用 gtk_tree_model_get_value,根据存储在树模型中的值(如果任何值通过 gtk_tree_view_column_add_attribute 或执行相同操作的任何一个便利函数进行映射)来设置单元格渲染器属性,然后将检索到的 GValue 传递给 g_object_set_property。

GtkCellRendererText、UTF8 和 pango 标记

[编辑 | 编辑源代码]

在 Gtk+-2.0 小部件中使用的所有文本都需要使用 UTF8 编码,GtkCellRendererText 也不例外。纯 ASCII 文本自动有效 UTF8,但只要您有不在纯 ASCII 中的特殊字符(通常是不在英语字母表中的字符),它们就需要使用 UTF8 编码。存在许多不同的字符编码,它们都指定了不同的方式来告诉计算机要使用哪个字符。Gtk+-2.0 使用 UTF8,每当您有使用不同编码的文本时,首先需要使用 GLib g_convert 函数系列中的一个函数将其转换为 UTF8 编码。如果您只使用其他 Gtk+ 小部件的文本输入,那么您就很安全,因为它们也会以 UTF8 格式返回所有文本。

但是,如果您使用“外部”文本输入源,则必须将文本从文本的编码(或用户的区域设置)转换为 UTF8,否则它将无法正确渲染(要么根本不渲染,要么在第一个无效字符后被截断)。文件名特别难处理,因为没有任何迹象表明文件名使用哪种字符编码(它可能是在用户使用其他区域设置时创建的,因此文件名编码本质上不可靠且已损坏)。在这种情况下,您可能希望使用回退字符转换为 UTF8。您可以使用 g_utf8_validate 检查字符串是否为有效的 UTF8。至少从作者的角度来看,您应该在代码的关键位置(无论是在性能方面是否受到影响,尤其是在您是使用非英语区域设置经验很少的英语程序员时)放置这些检查。这将使其他人和您自己更容易在以后发现与非英语区域设置相关的问题。

除了 "text" 属性之外,GtkCellRendererText 还具有一个 "markup" 属性,它接收包含 pango 标记的文本作为输入。Pango 标记允许您将特殊标签放置到文本字符串中,从而影响文本的渲染样式(参见 pango 文档)。基本上,您可以使用 pango 标记实现其他属性可以实现的所有功能(只是使用属性更有效率,也更简洁)。但是,Pango 标记有一个独特的优势,是文本单元格渲染器属性无法实现的:使用 pango 标记,您可以在文本中间更改文本样式,因此您可以例如,以粗体字体渲染文本字符串的一部分,而以正常字体渲染文本的其余部分。以下是一个包含 pango 标记的字符串示例:

"您可以使用 粗体其他颜色"

当使用“markup”属性时,您需要考虑到“markup”和“text”属性似乎并不相互排斥(我认为这可以称为一个bug)。换句话说:每当您设置“markup”(并且之前已经使用过“text”属性)时,请将“text”属性设置为NULL,反之亦然。示例

  ...

  void
  foo_cell_data_function ( ... )
  {
    ...
    if (foo->is_important)
      g_object_set(renderer, "markup", "<b>important</b>", "text", NULL, NULL);
    else
      g_object_set(renderer, "markup", NULL, "text", "not important", NULL);
    ...
  }

  ...

使用 pango 标记文本时,还需要牢记的一点是,如果使用随机输入数据动态构建包含 pango 标记的字符串,您可能需要对文本进行转义。例如

  ...

  void
  foo_cell_data_function ( ... )
  {
    gchar *markuptxt;

    ...
    /* This might be problematic if artist_string or title_string
     *   contain markup characters/entities: */
    markuptxt = g_strdup_printf("<b>%s</b> - <i>%s</i>",
                                artist_string, title_string);
    ...
    g_object_set(renderer, "markup", markuptxt, "text", NULL, NULL);
    ...
    g_free(markuptxt);
  }

  ...

如果 artist_string 为“Simon & Garfunkel”,上面的示例将无法工作,因为“&”字符是特殊字符之一。它们需要被转义,以便 pango 知道它们不指任何 pango 标记,而仅仅是字符。在这种情况下,字符串需要是“Simon &amp; Garfunkel”才能在要粘贴其中的 pango 标记之间有意义。可以使用 g_markup_escape 转义字符串(并且需要使用 g_free 再次释放由此产生的新分配的字符串)。

可以将 pango 标记和文本单元格渲染器属性结合使用。两者将被“添加”在一起以渲染目标字符串,只是文本单元格渲染器属性将应用于整个字符串。如果将“markup”属性设置为不包含任何 pango 标记的普通文本,它将像使用“text”属性一样以普通文本形式渲染。但是,与“text”属性相反,“markup”属性文本中的特殊字符仍然需要转义,即使文本中不使用 pango 标记。

一个工作示例

[编辑 | 编辑源代码]

这是我们一开始的示例(虽然添加了一列),只是模型的内容这次在屏幕上正确渲染。为了演示目的,使用了属性和单元格数据函数。

#include <gtk/gtk.h>

enum
{
  COL_FIRST_NAME = 0,
  COL_LAST_NAME,
  COL_YEAR_BORN,
  NUM_COLS
} ;

static GtkTreeModel *
create_and_fill_model (void)
{
  GtkTreeStore  *treestore;
  GtkTreeIter    toplevel, child;

  treestore = gtk_tree_store_new(NUM_COLS,
                                 G_TYPE_STRING,
                                 G_TYPE_STRING,
                                 G_TYPE_UINT);

  /* Append a top level row and leave it empty */
  gtk_tree_store_append(treestore, &toplevel, NULL);
  gtk_tree_store_set(treestore, &toplevel,
                     COL_FIRST_NAME, "Maria",
                     COL_LAST_NAME, "Incognito",
                     -1);

  /* Append a second top level row, and fill it with some data */
  gtk_tree_store_append(treestore, &toplevel, NULL);
  gtk_tree_store_set(treestore, &toplevel,
                     COL_FIRST_NAME, "Jane",
                     COL_LAST_NAME, "Average",
                     COL_YEAR_BORN, (guint) 1962,
                     -1);

  /* Append a child to the second top level row, and fill in some data */
  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child,
                     COL_FIRST_NAME, "Janinita",
                     COL_LAST_NAME, "Average",
                     COL_YEAR_BORN, (guint) 1985,
                     -1);

  return GTK_TREE_MODEL(treestore);
}

void
age_cell_data_func (GtkTreeViewColumn *col,
                    GtkCellRenderer   *renderer,
                    GtkTreeModel      *model,
                    GtkTreeIter       *iter,
                    gpointer           user_data)
{
  guint  year_born;
  guint  year_now = 2003; /* to save code not relevant for the example */
  gchar  buf[64];

  gtk_tree_model_get(model, iter, COL_YEAR_BORN, &year_born, -1);

  if (year_born <= year_now && year_born > 0)
  {
    guint age = year_now - year_born;

    g_snprintf(buf, sizeof(buf), "%u years old", age);

    g_object_set(renderer, "foreground-set", FALSE, NULL); /* print this normal */
  }
  else
  {
    g_snprintf(buf, sizeof(buf), "age unknown");

    /* make red */
    g_object_set(renderer, "foreground", "Red", "foreground-set", TRUE, NULL);
  }

  g_object_set(renderer, "text", buf, NULL);
}


static GtkWidget *
create_view_and_model (void)
{
  GtkTreeViewColumn   *col;
  GtkCellRenderer     *renderer;
  GtkWidget           *view;
  GtkTreeModel        *model;

  view = gtk_tree_view_new();

  /* --- Column #1 --- */

  col = gtk_tree_view_column_new();

  gtk_tree_view_column_set_title(col, "First Name");

  /* pack tree view column into tree view */
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);

  renderer = gtk_cell_renderer_text_new();

  /* pack cell renderer into tree view column */
  gtk_tree_view_column_pack_start(col, renderer, TRUE);

  /* connect 'text' property of the cell renderer to
   *  model column that contains the first name */
  gtk_tree_view_column_add_attribute(col, renderer, "text", COL_FIRST_NAME);


  /* --- Column #2 --- */

  col = gtk_tree_view_column_new();

  gtk_tree_view_column_set_title(col, "Last Name");

  /* pack tree view column into tree view */
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);

  renderer = gtk_cell_renderer_text_new();

  /* pack cell renderer into tree view column */
  gtk_tree_view_column_pack_start(col, renderer, TRUE);

  /* connect 'text' property of the cell renderer to
   *  model column that contains the last name */
  gtk_tree_view_column_add_attribute(col, renderer, "text", COL_LAST_NAME);

  /* set 'weight' property of the cell renderer to
   *  bold print (we want all last names in bold) */
  g_object_set(renderer,
               "weight", PANGO_WEIGHT_BOLD,
               "weight-set", TRUE,
               NULL);


  /* --- Column #3 --- */

  col = gtk_tree_view_column_new();

  gtk_tree_view_column_set_title(col, "Age");

  /* pack tree view column into tree view */
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);

  renderer = gtk_cell_renderer_text_new();

  /* pack cell renderer into tree view column */
  gtk_tree_view_column_pack_start(col, renderer, TRUE);

  /* connect a cell data function */
  gtk_tree_view_column_set_cell_data_func(col, renderer, age_cell_data_func, NULL, NULL);


  model = create_and_fill_model();

  gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);

  g_object_unref(model); /* destroy model automatically with view */

  gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)),
                              GTK_SELECTION_NONE);

  return view;
}


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

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  g_signal_connect(window, "delete_event", gtk_main_quit, NULL); /* dirty */

  view = create_view_and_model();

  gtk_container_add(GTK_CONTAINER(window), view);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

如何使整行变粗体或彩色

[编辑 | 编辑源代码]

这似乎是一个经常被问到的问题,因此值得在这里提一下。您有上面提到的两种方法:要么使用单元格数据函数,并在其中检查是否应以特定方式(粗体、彩色等)突出显示特定行,然后相应地设置渲染器属性(如果您想使该行看起来正常,则取消设置它们),要么使用属性。但是,在这种情况下,单元格数据函数很可能不是正确选择。

如果您只想使每第二行具有灰色背景,以便用户更容易地看到哪些数据属于哪一行(在宽树视图中),那么您不必理会这里提到的内容。相反,只需根据此处描述的内容设置树视图上的规则提示,所有操作都会自动完成,甚至以符合所选主题的颜色完成(除非主题禁用了规则提示)。

否则,最适合大多数情况的方法是,您向模型添加两列,一列用于属性本身(例如,类型为 G_TYPE_STRING 的 COL_ROW_COLOR 列),另一列用于该属性的布尔标志(例如,类型为 G_TYPE_BOOLEAN 的 COL_ROW_COLOR_SET 列)。然后,您将这些列与每个渲染器的“foreground”和“foreground-set”属性连接起来。现在,每当您将行的 COL_ROW_COLOR 字段设置为颜色,并将该行的 COL_ROW_COLOR_SET 字段设置为 TRUE 时,该列将以您选择的颜色渲染。如果您只想使用默认文本颜色或一种特殊的其他颜色,您甚至可以使用一列额外的模型列来实现相同的效果:在这种情况下,您可以将所有渲染器的“foreground”属性设置为所需的任何特殊颜色,并仅使用属性将 COL_ROW_COLOR_SET 列连接到所有渲染器的“foreground-set”属性。这类似于任何其他属性,只是您需要根据需要调整该属性的数据类型(例如,“weight”将使用 G_TYPE_INT,在这种情况下,将使用 PANGO_WEIGHT_FOO 定义)。

作为一般规则,除非有充分的理由,否则不应更改单元格的文本颜色或背景颜色。引用 Havoc Pennington 的话:“因为 GTK+ 中的颜色代表用户选择的主题,所以您永远不应该仅出于美观目的设置颜色。如果用户不喜欢 GTK+ 灰色,他们可以自己更改为他们喜欢的橙色阴影。”

如何将图标打包到树视图中

[编辑 | 编辑源代码]

到目前为止,我们只在树视图中放置了文本。虽然上一节介绍了显示图标(以 GdkPixbufs 形式)所需的一切,但简短的示例可能会帮助您更好地理解。以下代码将图标和一些文本打包到同一树视图列中

  enum
  {
    COL_ICON = 0,
    COL_TEXT,
    NUM_COLS
  };

  GtkListStore *
  create_liststore(void)
  {
    GtkListStore  *store;
    GtkTreeIter    iter;
    GdkPixbuf     *icon;
    GError        *error = NULL;

    store = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);

    icon = gdk_pixbuf_new_from_file("icon.png", &error);
    if (error)
    {
      g_warning ("Could not load icon: %s\n", error->message);
      g_error_free(error);
      error = NULL;
    }

    gtk_list_store_append(store, &iter);
    gtk_list_store_set(store, &iter,
                       COL_ICON, icon,
                       COL_TEXT, "example",
                       -1);

    return store;
  }

  GtkWidget *
  create_treeview(void)
  {
    GtkTreeModel      *model;
    GtkTreeViewColumn *col;
    GtkCellRenderer   *renderer;
    GtkWidget         *view;

    model = GTK_TREE_MODEL(create_liststore());

    view = gtk_tree_view_new_with_model(model);

    col = gtk_tree_view_column_new();
    gtk_tree_view_column_set_title(col, "Title");

    renderer = gtk_cell_renderer_pixbuf_new();
    gtk_tree_view_column_pack_start(col, renderer, FALSE);
    gtk_tree_view_column_set_attributes(col, renderer,
                                        "pixbuf", COL_ICON,
                                        NULL);

    renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_column_pack_start(col, renderer, TRUE);
    gtk_tree_view_column_set_attributes(col, renderer,
                                        "text", COL_TEXT,
                                        NULL);

    gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);

    gtk_widget_show_all(view);

    return view;
  }


请注意,树视图不会为您调整图标大小,而是按其原始大小显示它们。如果您想显示库存图标而不是从文件加载的 GdkPixbufs,您应该查看 GtkCellRendererPixbuf 的“stock-id”属性(并且您的模型列应为 G_TYPE_STRING 类型,因为所有库存 ID 都只是用于标识库存图标的字符串)。

华夏公益教科书