跳轉到內容

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 都只是用於標識庫存圖示的字串)。

華夏公益教科書