【问题标题】:GTK in C: Disabling Expander Widget on Treeview Parent NodeC 中的 GTK:禁用 Treeview 父节点上的扩展器小部件
【发布时间】:2021-02-03 22:07:48
【问题描述】:

我有一个使用 GTK 构建的 Treeview,其中有几个父节点,前两个父节点有几个子节点。第三个父节点有一个复选框,当它处于活动状态时,我希望它折叠第二个父节点并禁用它的扩展器,以便它的子节点不再可见并且无法访问。我在 Stack Overflow 上看到了其他几篇提到类似内容的帖子:

Making rows in a GTK TreeView unselectable

Disable expanding of all top level nodes of a treeview

第一个使用gtk_tree_selection_set_select_function (),第二个使用gtk_tree_model_filter_set_visible_func ()。我尝试了两种方法都无济于事,但第二种方法对我有用(它应该隐藏父节点,而不是停用扩展器)。我想我忽略了一些东西。当我激活 Parent 3 的复选框时,打印语句会从 visible_func 内部出现,但过滤模型不会影响可见性。我想我忽略了visible_func 功能方面的一些东西,但我不确定是什么。一个最小的例子如下:

// gcc -Wall -Wextra -o treeview treeview.c $(pkg-config gtk+-3.0 --cflags --libs) -rdynamic -lm
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

int check1 = 0, check2 = 0, check3 = 0, check4 = 0, check5 = 0, check6 = 0, check7 = 0;
double scale1 = 1.0, scale2 = 1.0, scale3 = 1.0, scale4 = 1.0, scale5 = 1.0, scale6 = 1.0, scale7 = 1.0;

GtkTreeView *treeview;
GtkWidget *view;

bool second_parent_access = true;

// These are the columns in the TreeView, enum is used to easily step through iter (i.e. parent -> child)
enum
{
  BUTTON_VIS = 0,
  BOLDNESS = 1,
  BUTTON = 2,
  CURRENT = 3,
  SCALE_VIS = 4,
  SCALE = 5,
  LABEL = 6,
  NUM_COLS = 7
};

// Event Handler for GtkCellRendererToggle being toggled
void on_cellrenderertoggle_toggled(__attribute__((unused)) GtkCellRendererToggle *cell_renderer, gchar *path, __attribute__((unused)) gpointer user_data)
{
  gboolean value;
  gboolean active;
  GtkTreeModel *model;
  GtkTreeIter iter;
  GtkTreeView *treeview;

  treeview = GTK_TREE_VIEW(view);

  // Toggle the cell renderer's current state to the logical not
  model = gtk_tree_view_get_model(treeview);

  if (gtk_tree_model_get_iter_from_string(model, &iter, path))
  {
    // Get the current state of the toggle button
    gtk_tree_model_get(model, &iter, BUTTON, &value, -1);
    // Set the state of the toggle button to the opposite of what it was
    gtk_tree_store_set(GTK_TREE_STORE(model), &iter, BUTTON, !value, -1);    
  }

  active = !value;

  // The form of the path is <parent>:<child> so for example we would have 3:4. We use the token ":" as 
  // the delimiter and split the string then convert from ASCII to integer.
  gchar **split_result = g_strsplit(path, ":", 0); // split_result[0] = parent, split_result[1] = child
  gint parent = g_ascii_strtoll(split_result[0], NULL, 10);
  g_strfreev(split_result);

  if (parent == 2) {
    if (active) {
      const gchar *path = "1";
      GtkTreePath *second_parent_path = gtk_tree_path_new_from_string (path);
      gboolean expanded;

      // Find out if Second Parent is expanded, if so collapse it
      expanded = gtk_tree_view_row_expanded(treeview, second_parent_path);

      if (expanded == TRUE)
        gtk_tree_view_collapse_row(treeview, second_parent_path);

      second_parent_access = false;
    }
    else 
      second_parent_access = true;
  }
}

// This event handler applies the changed text to the GtkCellRendererSpin cell in the TreeView.
static void scale_cell_edited(GtkCellRendererText *renderer, gchar *path, gchar *new_text, __attribute__((unused)) gpointer user_data)
{
  GtkTreeIter iter;
  GtkTreeModel *model;
  GtkAdjustment *adjustment;
  gdouble value;

  // Retrieve the current value stored by the spin button renderer's adjustment
  g_object_get(renderer, "adjustment", &adjustment, NULL);
  value = gtk_adjustment_get_value(adjustment);

  model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));

  // It is the responsibility of the application to update the model and store new_text at the position indicated by path
  // This is a string and needs to be converted to a double
  value = atof(new_text);

  if(gtk_tree_model_get_iter_from_string(model, &iter, path))
  {
    gtk_tree_store_set(GTK_TREE_STORE(model), &iter, SCALE, value, -1);
  }
}

// This function creates and returns the GtkTreeModel (which is the interface needed by the GtkTreeStore)
GtkTreeModel *create_and_fill_model(void)
{
  GtkTreeStore *treestore;
  GtkTreeIter toplevel, child;

  // Creates a new tree store with NUM_COLS columns each of the types passed in
  treestore = gtk_tree_store_new(NUM_COLS, G_TYPE_BOOLEAN, G_TYPE_INT, G_TYPE_BOOLEAN,
                                 G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_FLOAT, G_TYPE_INT);

  gtk_tree_store_append(treestore, &toplevel, NULL);
  gtk_tree_store_set(treestore, &toplevel, BUTTON_VIS, FALSE, BOLDNESS, 500, CURRENT,
                     "First Parent", SCALE_VIS, FALSE, LABEL, 1, -1);


  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child, BUTTON_VIS, TRUE, BUTTON, check1, CURRENT,
                     "First Child", SCALE_VIS, TRUE, SCALE, scale1, LABEL, 2, -1);

  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child, BUTTON_VIS, TRUE, BUTTON, check2, CURRENT,
                     "Second Child", SCALE_VIS, TRUE, SCALE, scale2, LABEL, 3, -1);

  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child, BUTTON_VIS, TRUE, BUTTON, check3, CURRENT,
                     "Third Child", SCALE_VIS, TRUE, SCALE, scale3, LABEL, 4, -1);

  gtk_tree_store_append(treestore, &toplevel, NULL);
  gtk_tree_store_set(treestore, &toplevel, BUTTON_VIS, FALSE, BOLDNESS, 500, CURRENT,
                     "Second Parent", SCALE_VIS, FALSE, LABEL, 5, -1);

  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child, BUTTON_VIS, TRUE, BUTTON, check4, CURRENT,
                     "First Child", SCALE_VIS, TRUE, SCALE, scale4, LABEL, 6, -1);

  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child, BUTTON_VIS, TRUE, BUTTON, check5, CURRENT,
                     "Second Child", SCALE_VIS, TRUE, SCALE, scale5, LABEL, 7, -1);

  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child, BUTTON_VIS, TRUE, BUTTON, check6, CURRENT,
                     "Third Child", SCALE_VIS, TRUE, SCALE, scale6, LABEL, 8, -1);

  gtk_tree_store_append(treestore, &toplevel, NULL);
  gtk_tree_store_set(treestore, &toplevel, BUTTON_VIS, TRUE, BUTTON, check7, BOLDNESS, 500, CURRENT,
                     "Third Parent", SCALE_VIS, TRUE, SCALE, scale7, LABEL, 9, -1);

  return GTK_TREE_MODEL(treestore);
}

// This function creates the container for the GtkTreeModel (interface) and the GtkTreeStore (data store)
GtkWidget *create_view_and_model()
{
  //GtkWidget *view;
  GtkTreeModel *model;

  view = gtk_tree_view_new();

  // COLUMN ONE POINTERS
  // Nothing to be rendered

  // COLUMN TWO POINTERS
  // Nothing to be rendered

  // COLUMN THREE POINTERS
  GtkTreeViewColumn *column3;
  GtkCellRenderer *check_box;
  check_box = gtk_cell_renderer_toggle_new();

  // COLUMN FOUR POINTERS
  GtkTreeViewColumn *column4;
  GtkCellRenderer *text;
  text = gtk_cell_renderer_text_new();

  // COLUMN FIVE POINTERS
  // Nothing to be rendered

  // COLUMN SIX POINTERS
  GtkTreeViewColumn *column6;
  GtkCellRenderer *mag_spin;
  mag_spin = gtk_cell_renderer_spin_new();
  GtkAdjustment * mag_adj;

  // COLUMN SEVEN POINTERS
  // Nothing to be rendered

  // *********COLUMN ONE*********
  // This stores a boolean value for visibility of Column THREE - there are no contents to be rendered

  // *********COLUMN TWO*********
  // This stores an integer value for font weight (i.e. "boldness" of text) of Column FOUR - nothing to render

  // *********COLUMN THREE*********
  // This is the checkbox column
  column3 = gtk_tree_view_column_new();

  // Set column title
  gtk_tree_view_column_set_title(column3, "");

  // Set fixed width for the column
  gtk_tree_view_column_set_fixed_width(column3, 50);

  // Pack tree view column into tree view
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), column3);

  // Pack cell renderer into tree view column
  gtk_tree_view_column_pack_start(column3, check_box, TRUE);

  // Add the check box property to the column
  gtk_tree_view_column_add_attribute(column3, check_box, "active", BUTTON);

  // Add the visibility attribute to the column
  gtk_tree_view_column_add_attribute(column3, check_box, "visible", BUTTON_VIS);

  // Connect the signal for clicking the checkboxes in the treeview
  g_signal_connect(G_OBJECT(check_box), "toggled", G_CALLBACK(on_cellrenderertoggle_toggled), NULL);

  // *********COLUMN FOUR*********
  // This is a string column listing the names for channel/current
  column4 = gtk_tree_view_column_new();

  // Set Column Title
  gtk_tree_view_column_set_title(column4, "Label");

  // Pack tree view column into tree view
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), column4);

  // Pack cell renderer into tree view column
  gtk_tree_view_column_pack_start(column4, text, TRUE);

  // Set the properties on the cell renderer for adjusting boldness
  g_object_set(text, "weight-set", TRUE, NULL);

  // Add the boldness property to the column
  gtk_tree_view_column_add_attribute(column4, text, "weight", BOLDNESS);

  // Add the text property to the column
  gtk_tree_view_column_add_attribute(column4, text, "text", CURRENT);

  // *********COLUMN FIVE*********
  // This stores a boolean value for visibility of Column SIX - there are no contents to be rendered

  // *********COLUMN SIX*********
  // This is a GtkSpinButton column for adjusting the magnification of each current

  // Sets the GtkAdjustment for the scale
  // Arguments are initial value, min value, max value, step increment, page increment, and page size
  mag_adj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 100.0, 1.0, 1.0, 1.0));

  // Set the properties on the cell renderer for adjusting values
  g_object_set(mag_spin, "editable", TRUE, "adjustment", mag_adj, "digits", 3, NULL);

  // Connect the signal to the event handler that applies the changed text to the GtkCellRendererSpin cell
  g_signal_connect(G_OBJECT(mag_spin), "edited", G_CALLBACK(scale_cell_edited), NULL);

  // Create the column and add the attributes
  column6 = gtk_tree_view_column_new_with_attributes("Spin Button", mag_spin, "text", SCALE, NULL);

  // Pack tree view column into tree view
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), column6);

  // Add the visibility attribute to the column
  gtk_tree_view_column_add_attribute(column6, mag_spin, "visible", SCALE_VIS);

  // *********COLUMN SEVEN*********
  // This stores an integer to label the row for when it needs to be referenced upon
  // clicking to show the correct page in the GtkStack.

  // *********CREATE THE TreeModel*********

  // Hide the column header
  gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), TRUE);

  // Connect the model created above
  model = create_and_fill_model();
  gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);
  g_object_unref(model);

  return view;
}

// This function allows Second Parent to become invisible when the user checks Third Parent's checkbox
static gboolean visible_func (GtkTreeModel *model, GtkTreeIter *iter, __attribute__((unused)) gpointer user_data)
{
  GtkTreePath *path;
  path = gtk_tree_model_get_path (model, iter);
  gchar *path_string = NULL;
  path_string = gtk_tree_path_to_string(path);
  gboolean visible = TRUE;

  if (g_strcmp0(path_string, "2") == 0 && second_parent_access) {

    g_print("Turning visibility of Second Parent off.\n");
    visible = FALSE;
  }

  return visible;
}

static void destroy(__attribute__((unused)) GtkWidget *widget, __attribute__((unused)) gpointer user_data)
{
    gtk_main_quit ();
}

int main(int argc, char *argv[])
{
  // Initialize Gtk - used in lieu of GtkApplication
  gtk_init(&argc, &argv);

  GtkWidget *window;
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  // Link the GtkWidget containing the GtkTreeView to the Window container
  view = create_view_and_model();
  treeview = GTK_TREE_VIEW(view);

  gtk_container_add(GTK_CONTAINER(window), view);

  GtkTreeModel *tree_model;
  GtkTreeModel *filtered_model;

  tree_model = gtk_tree_view_get_model(treeview);
  filtered_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (tree_model), NULL);
  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filtered_model), visible_func, NULL, NULL);
  gtk_tree_view_set_model(treeview, gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(filtered_model)));

  g_signal_connect (window, "destroy", G_CALLBACK (destroy), NULL);

  // Show Main Window Widget
  gtk_widget_show_all(GTK_WIDGET(window));

  gtk_main();

  return 0;
}

【问题讨论】:

  • 我可能遗漏了一些东西,但你不会把孩子们带走吗?
  • 那么您是否认为我应该有两个单独的树视图,一个有孩子,一个没有孩子,然后每次都重建?问题是,对于仅停用固有扩展器小部件的小细节,这条路线似乎非常麻烦。起初我以为我可以让它变得不敏感,但 API 中似乎没有任何东西可以做到这一点......
  • 扩展器仅在父母有孩子时显示。我想,去掉孩子,扩展器就消失了。

标签: c treeview gtk


【解决方案1】:

按照 Gtknerd 的建议,我使用的解决方案是有两个独立的模型,一个有孩子,一个没有孩子。我想认为有比这更清洁的解决方案,但它确实可以解决问题。在我设置的on_cellrenderertoggle_toggled 回调中:

  if (parent == 2) {
    if (active) {
      check7 = 1;

      model = create_and_fill_filtered_model();
      gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);

      g_print("ACTIVE\n");
    }
    else {
      check7 = 0;

      model = create_and_fill_model();
      gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);

      g_print("INACTIVE\n");
    }
  }

然后我有另一个模型删除了 Second Parent 的孩子。这被定义为:

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

  // Creates a new tree store with NUM_COLS columns each of the types passed in
  treestore = gtk_tree_store_new(NUM_COLS, G_TYPE_BOOLEAN, G_TYPE_INT, G_TYPE_BOOLEAN,
                                 G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_FLOAT, G_TYPE_INT);

  gtk_tree_store_append(treestore, &toplevel, NULL);
  gtk_tree_store_set(treestore, &toplevel, BUTTON_VIS, FALSE, BOLDNESS, 500, CURRENT,
                     "First Parent", SCALE_VIS, FALSE, LABEL, 1, -1);


  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child, BUTTON_VIS, TRUE, BUTTON, check1, CURRENT,
                     "First Child", SCALE_VIS, TRUE, SCALE, scale1, LABEL, 2, -1);

  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child, BUTTON_VIS, TRUE, BUTTON, check2, CURRENT,
                     "Second Child", SCALE_VIS, TRUE, SCALE, scale2, LABEL, 3, -1);

  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child, BUTTON_VIS, TRUE, BUTTON, check3, CURRENT,
                     "Third Child", SCALE_VIS, TRUE, SCALE, scale3, LABEL, 4, -1);

  gtk_tree_store_append(treestore, &toplevel, NULL);
  gtk_tree_store_set(treestore, &toplevel, BUTTON_VIS, FALSE, BOLDNESS, 500, CURRENT,
                     "Second Parent", SCALE_VIS, FALSE, LABEL, 5, -1);

  gtk_tree_store_append(treestore, &toplevel, NULL);
  gtk_tree_store_set(treestore, &toplevel, BUTTON_VIS, TRUE, BUTTON, check7, BOLDNESS, 500, CURRENT,
                     "Third Parent", SCALE_VIS, TRUE, SCALE, scale7, LABEL, 6, -1);

  return GTK_TREE_MODEL(treestore);
}

请注意,不再需要全局 second_parent_access 布尔标志,因为在重建树时所有父菜单都会崩溃。这样就有了所需的功能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-01-02
    • 2019-05-19
    • 1970-01-01
    • 1970-01-01
    • 2010-09-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多