跳轉到內容

GTK+ 例項/樹形檢視/事件

來自華夏公益教科書

選擇、雙擊和上下文選單

[編輯 | 編輯原始碼]

處理選擇

[編輯 | 編輯原始碼]

列表或樹形檢視最基本的功能之一是,可以選中或取消選中行。選擇使用樹形檢視的 GtkTreeSelection 物件進行處理。每個樹形檢視都自動與一個 GtkTreeSelection 相關聯,您可以使用 gtk_tree_view_get_selection 獲取它。選擇完全在樹形檢視側處理,這意味著模型不知道哪些行被選中。沒有特別的理由說明選擇處理不能透過訪問樹形檢視小部件的函式來實現,但出於 API 清潔度和程式碼清晰度的考慮,GTK+ 開發人員決定建立一個特殊的 GtkTreeSelection 物件,然後在內部處理樹形檢視小部件。您永遠不需要建立選擇物件,它會在您建立新的樹形檢視時自動為您建立。您只需要使用 gtk_tree_view_get_selection 函式來獲取指向選擇物件的指標。

有三種方法可以處理樹形檢視選擇:您可以獲取當前選中行的列表,例如在上下文選單函式中,或者您可以跟蹤所有選中和取消選中操作並儲存當前選中行的列表,以便在需要時使用它們;作為最後的手段,您也可以遍歷您的列表或樹,並檢查每個單獨的行是否被選中(例如,如果您想要所有未選中的行,您需要這樣做)。

選擇模式

[編輯 | 編輯原始碼]

您可以使用 gtk_tree_selection_set_mode 來影響選擇處理的方式。有四種選擇模式

  • GTK_SELECTION_NONE - 無法選擇任何專案
  • GTK_SELECTION_SINGLE - 只能選擇一個專案
  • GTK_SELECTION_BROWSE - 始終只選擇一個專案
  • GTK_SELECTION_MULTIPLE - 可以選擇任何專案,從沒有專案到所有專案都可以

獲取當前選中的行

[編輯 | 編輯原始碼]

您可以透過使用 gtk_tree_selection_selected_foreach 遍歷所有選中的行來訪問當前選中的行,或者使用 gtk_tree_selection_get_selected_rows 獲取選中行的樹路徑的 GList。請注意,此函式僅在 Gtk+-2.2 及更高版本中可用,這意味著您不能使用它,或者如果您希望您的應用程式與舊版本安裝一起使用,則需要重新實現它。

如果您使用的選擇模式是 GTK_SELECTION_SINGLE 或 GTK_SELECTION_BROWSE,則獲取選中行的最方便的方法是使用 gtk_tree_selection_get_selected 函式,該函式將返回 TRUE 並用指定的樹迭代填充選中行(如果選擇了行),否則返回 FALSE。它用於以下方式

  ...

  GtkTreeSelection *selection;
  GtkTreeModel     *model;
  GtkTreeIter       iter;

  /* This will only work in single or browse selection mode! */

  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
  if (gtk_tree_selection_get_selected(selection, &model, &iter))
  {
    gchar *name;

    gtk_tree_model_get (model, &iter, COL_NAME, &name, -1);

    g_print ("selected row is: %s\n", name);

    g_free(name);
  }
  else
  {
    g_print ("no row selected.\n");
  }

  ...

您需要注意的一點是,在 gtk_tree_selection_selected_foreach 回撥中,或者在迴圈遍歷 gtk_tree_selection_get_selected_rows 返回的列表時,您需要小心刪除模型中的行(因為它包含路徑,並且當您刪除中間的行時,舊路徑將指向不存在的行,或者指向與所選行不同的行)。您有兩種方法可以解決此問題:一種方法是使用上面描述的刪除多行的方法,即獲取所有選中行的樹行引用,然後逐個刪除這些行;另一種解決方案是對選中樹路徑的列表進行排序,以便最後一行在列表中排在最前面,這樣您就可以從列表或樹的末尾刪除行。無論如何,您都不能在 foreach 回撥中刪除行,這根本不允許。

以下是使用 gtk_tree_selection_selected_foreach 的示例

  ...

  gboolean
  view_selected_foreach_func (GtkTreeModel  *model,
                              GtkTreePath   *path,
                              GtkTreeIter   *iter,
                              gpointer       userdata)
  {
    gchar *name;

    gtk_tree_model_get (model, iter, COL_NAME, &name, -1);

    g_print ("%s is selected\n", name);
  }


  void
  do_something_with_all_selected_rows (GtkWidget *treeview)
  {
    GtkTreeSelection  *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));

    gtk_tree_selection_selected_foreach(selection, view_selected_foreach_func, NULL);
  }


  void
  create_view (void)
  {
    GtkWidget         *view;
    GtkTreeSelection  *selection;

    ...

    view = gtk_tree_view_new();

    ...

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));

    gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
    ...
  }

  ...

使用選擇函式

[編輯 | 編輯原始碼]

您可以使用 gtk_tree_selection_set_select_function 設定自定義選擇函式。每當要選中或取消選中行時,該函式將被呼叫(這意味著:它將在行的選擇狀態改變之前被呼叫)。選擇函式通常用於以下內容

  1. ... 跟蹤當前選中的專案(然後您自己維護一個選中專案的列表)。在這種情況下,請再次注意,您的選擇函式是在行的選擇狀態改變之前呼叫的。換句話說:如果行將要被選中,則傳遞給選擇函式的布林值 path_currently_selected 變數仍然為 FALSE。還要注意,當刪除行時,選擇函式可能並不總是被呼叫,因此您要麼在刪除行之前取消選中該行以確保您的選擇函式被呼叫並從您的列表中刪除該行,要麼在處理您儲存的選擇列表時檢查行的有效性。您不應該將樹路徑儲存在您自己維護的選中行列表中,因為每當新增或刪除行或對模型進行排序時,路徑都可能指向其他行。使用樹行引用或其他唯一方法來標識行。
  2. ... 告訴 GTK+ 是否允許選中或取消選中該特定行(但您應該確保使用者以其他方式清楚地知道是否可以選擇行,否則如果使用者無法選中或取消選中行,她將感到困惑)。這是透過在選擇函式中返回 TRUE 或 FALSE 來實現的。
  3. ... 在選中或取消選中行時執行其他操作。

另一個簡單的示例

  ...

  gboolean
  view_selection_func (GtkTreeSelection *selection,
                       GtkTreeModel     *model,
                       GtkTreePath      *path,
                       gboolean          path_currently_selected,
                       gpointer          userdata)
  {
    GtkTreeIter iter;

    if (gtk_tree_model_get_iter(model, &iter, path))
    {
      gchar *name;

      gtk_tree_model_get(model, &iter, COL_NAME, &name, -1);

      if (!path_currently_selected)
      {
        g_print ("%s is going to be selected.\n", name);
      }
      else
      {
        g_print ("%s is going to be unselected.\n", name);
      }

      g_free(name);
    }

    return TRUE; /* allow selection state to change */
  }


  void
  create_view (void)
  {
    GtkWidget         *view;
    GtkTreeSelection  *selection;

    ...

    view = gtk_tree_view_new();

    ...

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));

    gtk_tree_selection_set_select_function(selection, view_selection_func, NULL, NULL);
    ...
  }

  ...

檢查行是否被選中

[編輯 | 編輯原始碼]

您可以使用 gtk_tree_selection_iter_is_selected 或 gtk_tree_selection_path_is_selected 函式來檢查給定行是否被選中。例如,如果您想知道所有未選中的行,您可以簡單地遍歷整個列表或樹,並使用上述函式來檢查每行是否被選中。

選擇和取消選擇行

[編輯 | 編輯原始碼]

如果您需要手動選擇或取消選擇行,可以使用 gtk_tree_selection_select_iter、gtk_tree_selection_select_path、gtk_tree_selection_unselect_iter、gtk_tree_selection_unselect_path、gtk_tree_selection_select_all 和 gtk_tree_selection_unselect_all。

獲取選中的行數

[編輯 | 編輯原始碼]

有時您想知道當前選中的行數(例如,在彈出上下文選單之前設定上下文選單項的活動或非活動狀態)。如果您使用的是 GTK_SELECTION_SINGLE 或 GTK_SELECTION_BROWSE 選擇模式,則可以使用 gtk_tree_selection_get_selected 函式輕鬆地進行檢查,該函式將返回 TRUE 或 FALSE(表示選中一行或未選中任何行)。

如果您使用的是 GTK_SELECTION_MULTIPLE 或想要一種適用於所有選擇模式的更通用的方法,gtk_tree_selection_count_selected_rows 將返回您要查詢的資訊。該函式唯一的缺點是它只存在於 Gtk+-2.2 及更高版本中,因此如果您希望使用仍使用 Gtk+-2.0 的舊版本的使用者也能使用您的程式,則必須重新實現它。以下是重新實現此函式的一種方法。

  static void
  count_foreach_helper (GtkTreeModel *model,
                        GtkTreePath  *path,
                        GtkTreeIter  *iter,
                        gpointer      userdata)
  {
    gint *p_count = (gint*) userdata;

    g_assert (p_count != NULL);

    *p_count = *p_count + 1;
  }

  gint
  my_tree_selection_count_selected_rows (GtkTreeSelection *selection)
  {
    gint count = 0;

    gtk_tree_selection_selected_foreach(selection, count_foreach_helper, &count);

    return count;
  }

雙擊一行

[編輯 | 編輯原始碼]

捕獲雙擊一行非常容易,只需連線到樹檢視的“row-activated”訊號,如下所示

  void
  view_onRowActivated (GtkTreeView        *treeview,
                       GtkTreePath        *path,
                       GtkTreeViewColumn  *col,
                       gpointer            userdata)
  {
    GtkTreeModel *model;
    GtkTreeIter   iter;

    g_print ("A row has been double-clicked!\n");

    model = gtk_tree_view_get_model(treeview);

    if (gtk_tree_model_get_iter(model, &iter, path))
    {
       gchar *name;

       gtk_tree_model_get(model, &iter, COLUMN_NAME, &name, -1);

       g_print ("Double-clicked row contains name %s\n", name);

       g_free(name);
    }
  }

  void
  create_view (void)
  {
    GtkWidget *view;

    view = gtk_tree_view_new();

    ...

    g_signal_connect(view, "row-activated", (GCallback) view_onRowActivated, NULL);

    ...
  }

右鍵單擊時的上下文選單

[編輯 | 編輯原始碼]

上下文選單是上下文相關的選單,當用戶右鍵單擊列表或樹時會彈出,通常允許使用者對選定專案執行某些操作或以其他方式操作列表或樹。

右鍵單擊樹檢視就像捕獲任何其他小部件上的滑鼠按鈕單擊一樣,透過連線到樹檢視的“button_press_event”訊號處理程式來捕獲(這是 GtkWidget 訊號,並且由於 GtkTreeView 是從 GtkWidget 派生的,因此它也具有此訊號)。此外,您還應該連線到“popup-menu”訊號,以便使用者可以在沒有滑鼠的情況下訪問您的上下文選單。當用戶按下 Shift-F10 時,會發出“popup-menu”訊號。此外,您應該確保上下文選單中提供的所有功能也可以透過其他方式訪問,例如應用程式的主選單。有關更多詳細資訊,請參閱 GNOME 人機互動指南 (HIG)。直接從程式碼片段說明勝過千言萬語,以下是一些程式碼供您參考。

  void
  view_popup_menu_onDoSomething (GtkWidget *menuitem, gpointer userdata)
  {
    /* we passed the view as userdata when we connected the signal */
    GtkTreeView *treeview = GTK_TREE_VIEW(userdata);

    g_print ("Do something!\n");
  }


  void
  view_popup_menu (GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
  {
    GtkWidget *menu, *menuitem;

    menu = gtk_menu_new();

    menuitem = gtk_menu_item_new_with_label("Do something");

    g_signal_connect(menuitem, "activate",
                     (GCallback) view_popup_menu_onDoSomething, treeview);

    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);

    gtk_widget_show_all(menu);

    /* Note: event can be NULL here when called from view_onPopupMenu;
     *  gdk_event_get_time() accepts a NULL argument */
    gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
                   (event != NULL) ? event->button : 0,
                   gdk_event_get_time((GdkEvent*)event));
  }


  gboolean
  view_onButtonPressed (GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
  {
    /* single click with the right mouse button? */
    if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3)
    {
      g_print ("Single right click on the tree view.\n");

      /* optional: select row if no row is selected or only
       *  one other row is selected (will only do something
       *  if you set a tree selection mode as described later
       *  in the tutorial) */
      if (1)
      {
        GtkTreeSelection *selection;

        selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));

        /* Note: gtk_tree_selection_count_selected_rows() does not
         *   exist in gtk+-2.0, only in gtk+ >= v2.2 ! */
        if (gtk_tree_selection_count_selected_rows(selection)  <= 1)
        {
           GtkTreePath *path;

           /* Get tree path for row that was clicked */
           if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview),
                                             (gint) event->x, 
                                             (gint) event->y,
                                             &path, NULL, NULL, NULL))
           {
             gtk_tree_selection_unselect_all(selection);
             gtk_tree_selection_select_path(selection, path);
             gtk_tree_path_free(path);
           }
        }
      } /* end of optional bit */

      view_popup_menu(treeview, event, userdata);

      return TRUE; /* we handled this */
    }

    return FALSE; /* we did not handle this */
  }


  gboolean
  view_onPopupMenu (GtkWidget *treeview, gpointer userdata)
  {
    view_popup_menu(treeview, NULL, userdata);

    return TRUE; /* we handled this */
  }


  void
  create_view (void)
  {
    GtkWidget *view;

    view = gtk_tree_view_new();

    ...

    g_signal_connect(view, "button-press-event", (GCallback) view_onButtonPressed, NULL);
    g_signal_connect(view, "popup-menu", (GCallback) view_onPopupMenu, NULL);

    ...
  }
華夏公益教科書