GTK+ 示例/树形视图/杂项
本节讨论了一些似乎不适合放在其他地方的问题。如果您能想到其他应该在这里讨论的内容,请不要犹豫,发送邮件至 <tim at centricular dot net>。
信号回调函数通常只传递一个指向 GtkTreeViewColumn 的指针,而应用程序程序员实际上只想了解哪个列号受到了影响。有两种方法可以找出列在树形视图中的位置。一种方法是编写一个小的辅助函数,根据给定的树形视图列对象查找列号,例如:[1]。Gtk3:将 'col->tree_view' 替换为 'gtk_tree_view_column_get_tree_view(col)'
/* Returns column number or -1 if not found or on error */
gint
get_col_number_from_tree_view_column (GtkTreeViewColumn *col)
{
GList *cols;
gint num;
g_return_val_if_fail ( col != NULL, -1 );
g_return_val_if_fail ( col->tree_view != NULL, -1 );
cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(col->tree_view));
num = g_list_index(cols, (gpointer) col);
g_list_free(cols);
return num;
}
或者,可以使用 g_object_set_data 和 g_object_get_data 在树形视图列上识别它是哪一列。这还有一个优点,即即使列在树形视图中重新排序,您仍然可以跟踪您的列(尽管此功能通常是禁用的)。使用方法如下
...
enum
{
COL_FIRSTNAME,
COL_SURNAME,
};
...
void
some_callback (GtkWidget *treeview, ..., GtkTreeViewColumn *col, ...)
{
guint colnum = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(col), "columnnum"));
...
}
void
create_view(void)
{
...
col = gtk_tree_view_column_new();
g_object_set_data(G_OBJECT(col), "columnnum", GUINT_TO_POINTER(COL_FIRSTNAME));
...
col = gtk_tree_view_column_new();
g_object_set_data(G_OBJECT(col), "columnnum", GUINT_TO_POINTER(COL_SURNAME));
...
}
"columnnum" 是上面示例中的一个随机字符串 - 您可以使用任何您想要的字符串,或者存储多个数据位(当然使用不同的字符串标识符)。当然,您也可以组合这两种方法,因为它们做的事情略有不同(第一种方法跟踪列在树形视图中的“物理”位置,第二种方法跟踪列对您的“含义”,与它在视图中的位置无关)。注释 [1]
此函数的灵感来自此邮件列表消息(感谢 Ken Rastatter 的链接和主题建议)。
是否可以完全隐藏列扩展器?既可以又不可以。下面可能是最糟糕的黑客手段,无法保证它在即将发布的 Gtk+ 版本中或所有过去版本中都能正常工作(尽管后者很容易测试)。
您可以做的事情是创建一个空的树形视图列(例如,包含空字符串),并将其作为树形视图中的第一列。然后,您可以使用 gtk_tree_view_column_set_visible 隐藏该列。您会注意到,扩展器列现在会自动移动到以前位于第二列、现在位于第一列的可见列中。但是,如果您在调用 _set_visible 之后立即调用 gtk_tree_view_set_expander_column,则扩展器将移回隐藏的列,并且不再可见任何扩展器。
这意味着您必须自己处理行的展开和折叠,并使用相应的树形视图函数。虽然最后可以认为可以使用自定义单元格渲染器或 pixbuf 单元格渲染器实现自定义扩展器,但这可能是一项会让您忙超过五分钟的任务。如果您仍然尝试这样做,请准备好止痛药……
在某些情况下,即使相关行没有子节点,也应该显示扩展器,例如,当模型的一部分应该只有在请求时才能加载(例如,显示目录的内容)才会加载时。这是不可能的。只有当节点有子节点时,才会显示扩展器。
但是,这个问题存在一种解决方法:只需附加一个空的子行,并将节点设置为折叠状态。然后监听树形视图的 "row-expanded" 信号,并使用第一个新行填充已存在的行的内容,然后追加新的子行。有关更多详细信息,请参见此邮件列表主题。
似乎在许多情况下,当人们想知道点击事件发生的单元格渲染器时,他们并不真正需要知道单元格渲染器,而是想要修改特定列中的单个单元格。为此,您不需要知道单元格渲染器。使用 gtk_tree_view_get_path_at_pos 从传递给 "button-press-event" 信号回调中的按钮事件的 x 和 y 坐标获取树路径(如果您使用 "row-activated" 信号捕获双击,则会直接将树路径传递到回调函数中)。然后使用 gtk_tree_model_get_iter 将该树路径转换为迭代器,并使用 gtk_list_store_set 或 gtk_tree_store_set 修改要修改的单元格中的数据。
如果您确实需要知道发生按钮按下事件的单元格渲染器,那就有点棘手了。以下是对如何处理此问题的建议(该函数尚未经过充分测试,如果不同列中一个渲染器渲染的内容宽度不同,则可能无法正常工作;请将有关如何修复或改进此函数的建议发送给作者)
static gboolean
tree_view_get_cell_from_pos(GtkTreeView *view, guint x, guint y, GtkCellRenderer **cell)
{
GtkTreeViewColumn *col = NULL;
GList *node, *columns, *cells;
guint colx = 0;
g_return_val_if_fail ( view != NULL, FALSE );
g_return_val_if_fail ( cell != NULL, FALSE );
/* (1) find column and column x relative to tree view coordinates */
columns = gtk_tree_view_get_columns(view);
for (node = columns; node != NULL && col == NULL; node = node->next)
{
GtkTreeViewColumn *checkcol = (GtkTreeViewColumn*) node->data;
if (x >= colx && x < (colx + checkcol->width))
col = checkcol;
else
colx += checkcol->width;
}
g_list_free(columns);
if (col == NULL)
return FALSE; /* not found */
/* (2) find the cell renderer within the column */
cells = gtk_tree_view_column_get_cell_renderers(col);
for (node = cells; node != NULL; node = node->next)
{
GtkCellRenderer *checkcell = (GtkCellRenderer*) node->data;
guint width = 0, height = 0;
/* Will this work for all packing modes? doesn't that
* return a random width depending on the last content
* rendered? */
gtk_cell_renderer_get_size(checkcell, GTK_WIDGET(view), NULL, NULL, NULL, &width, NULL);
if (x >= colx && x < (colx + width))
{
*cell = checkcell;
g_list_free(cells);
return TRUE;
}
colx += width;
}
g_list_free(cells);
return FALSE; /* not found */
}
static gboolean
onButtonPress (GtkWidget *view, GdkEventButton *bevent, gpointer data)
{
GtkCellRenderer *renderer = NULL;
if (tree_view_get_cell_from_pos(GTK_TREE_VIEW(view), bevent->x, bevent->y, &renderer))
g_print ("Renderer found\n");
else
g_print ("Renderer not found!\n");
}
一个经常被问到的问题是,如何在 Glade 中向 GtkTreeView 添加列。[1] 答案基本上是,您不能这样做,而且您无法这样做。Glade/libglade 唯一能为您做的事情是为您创建一个没有任何内容的 GtkTreeView。您需要在应用程序启动时查找树形视图小部件(当然是在界面创建之后),并将您的列表存储或树存储连接到它。然后,您需要添加 GtkTreeViewColumns 和单元格渲染器来以您想要的方式显示模型中的信息。您需要在应用程序中完成所有这些操作。
另一种方法是从 GtkTreeView 派生您自己的特殊小部件,它按照您想要的方式设置所有内容,然后在 Glade 中使用“自定义小部件”功能。当然,这仍然意味着您必须编写所有代码来填充列和单元格渲染器,并自己创建模型。
- ↑ 不要使用 Glade 为您生成代码。使用 Glade 创建界面。它会将界面保存到一个 XML 格式的 .glade 文件中。然后,您可以使用 libglade2 从该 .glade 文件中构建您的界面(窗口等)。请参见 此邮件列表消息,了解关于为什么应该避免 Glade 代码生成的简短讨论。