跳轉到內容

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;
}
華夏公益教科書