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 设置自定义选择函数。每当要选中或取消选中行时,该函数将被调用(这意味着:它将在行的选择状态改变之前被调用)。选择函数通常用于以下内容
- ... 跟踪当前选中的项目(然后您自己维护一个选中项目的列表)。在这种情况下,请再次注意,您的选择函数是在行的选择状态改变之前调用的。换句话说:如果行将要被选中,则传递给选择函数的布尔值 path_currently_selected 变量仍然为 FALSE。还要注意,当删除行时,选择函数可能并不总是被调用,因此您要么在删除行之前取消选中该行以确保您的选择函数被调用并从您的列表中删除该行,要么在处理您保存的选择列表时检查行的有效性。您不应该将树路径存储在您自己维护的选中行列表中,因为每当添加或删除行或对模型进行排序时,路径都可能指向其他行。使用树行引用或其他唯一方法来标识行。
- ... 告诉 GTK+ 是否允许选中或取消选中该特定行(但您应该确保用户以其他方式清楚地知道是否可以选择行,否则如果用户无法选中或取消选中行,她将感到困惑)。这是通过在选择函数中返回 TRUE 或 FALSE 来实现的。
- ... 在选中或取消选中行时执行其他操作。
另一个简单的示例
...
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);
...
}