GTK+中的樹狀列表構件(GtkTreeView)
在本章的GTK+程序設計教程中,我們將向大家重點介紹非常常用也有點復雜的構件——GtkTreeView 。
GtkTreeView 構件是一個高級的構件,利用他你就可以制作出漂亮的普通列表或者是樹狀的列表。這個構件里可以包含一或者多行。他的構架呢?正是采用了大名鼎鼎的MVC (Model View Controller) 設計框架。也就是說數據和顯示方式是進行了一種分離的操作。
之前我們有說過復雜這個問題,於是在GtktreeView構件中確實還有着其他幾個獨立的對象結構(objects)。其中 GtkCellRenderer 就決定了在GtkTreeViewColumn. 中的數據究竟是如何來進行顯示呈現的。GtkListStore 和 GtkTreeStore 的功能為體現模型(model)的作用。也就是說他們是用來處理和分析將要在GtkTreeView顯示的數據的。 GtkTreeIter 則是一個數據結構被用於在GtkTreeView構件中,對行中的數據進行操作。 GtkTreeSelection 則是用來處理選項的。
一個簡單的列表構件示例(Simple List View)
在這個例子中將向大家展示一個簡單的列表效果。顯示的數據僅僅是文本。
#include <gtk/gtk.h>
enum
{
LIST_ITEM = 0,
N_COLUMNS
};
static void
init_list(GtkWidget *list)
{
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;
GtkListStore *store;
renderer = gtk_cell_renderer_text_new();
column = gtk_tree_view_column_new_with_attributes("List Items",
renderer, "text", LIST_ITEM, NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
store = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING);
gtk_tree_view_set_model(GTK_TREE_VIEW(list),
GTK_TREE_MODEL(store));
g_object_unref(store);
}
static void
add_to_list(GtkWidget *list, const gchar *str)
{
GtkListStore *store;
GtkTreeIter iter;
store = GTK_LIST_STORE(gtk_tree_view_get_model
(GTK_TREE_VIEW(list)));
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, LIST_ITEM, str, -1);
}
void on_changed(GtkWidget *widget, gpointer label)
{
GtkTreeIter iter;
GtkTreeModel *model;
char *value;
if (gtk_tree_selection_get_selected(
GTK_TREE_SELECTION(widget), &model, &iter)) {
gtk_tree_model_get(model, &iter, LIST_ITEM, &value, -1);
gtk_label_set_text(GTK_LABEL(label), value);
g_free(value);
}
}
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *list;
GtkWidget *vbox;
GtkWidget *label;
GtkTreeSelection *selection;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_container_set_border_width(GTK_CONTAINER(window), 10);
gtk_widget_set_size_request(window, 270, 250);
gtk_window_set_title(GTK_WINDOW(window), "List View");
list = gtk_tree_view_new();
gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
vbox = gtk_vbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), list, TRUE, TRUE, 5);
label = gtk_label_new("");
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
gtk_container_add(GTK_CONTAINER(window), vbox);
init_list(list);
add_to_list(list, "Aliens");
add_to_list(list, "Leon");
add_to_list(list, "Capote");
add_to_list(list, "Saving private Ryan");
add_to_list(list, "Der Untergang");
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
g_signal_connect(selection, "changed",
G_CALLBACK(on_changed), label);
g_signal_connect(G_OBJECT (window), "destroy",
G_CALLBACK(gtk_main_quit), NULL);
gtk_widget_show_all(window);
gtk_main ();
return 0;
}
在我們上面的這個示例代碼中,我們將向大家展示的是5個條目並布置於GtkTreeView 構件中。我們首先在window中放置一個GtkVBox 構件。 在這個 GtkVBox 構件中含有兩個構件:GtkTreeView和GtkLabel。
list = gtk_tree_view_new(); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
上面的代碼生成了一個 GtkTreeView 構件並且欄數被設置為FALSE即只有一欄。
label = gtk_label_new("");
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
生成了一個 GtkLabel構件,並且把它放置在GtkTreeView構件的下方,設置為居中。
init_list(list);
調用list()函數,初始化構件list。
renderer = gtk_cell_renderer_text_new();
column = gtk_tree_view_column_new_with_attributes("List Items",
renderer, "text", LIST_ITEM, NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
在初始化函數中,我們生成了只有一欄的GtkTreeView。
store = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING);
gtk_tree_view_set_model(GTK_TREE_VIEW(list),
GTK_TREE_MODEL(store));
接下來我們又生成了一個GtkListStore 構件(a model) 然后把它與list 構件綁定。
g_object_unref(store);
這個 model 被自動的銷毀,以釋放內存空間。
add_to_list(list, "Aliens");
上面就是在調用add_to_list()函數,實現向list 中在增加一個選項的功能。
store = GTK_LIST_STORE(gtk_tree_view_get_model
(GTK_TREE_VIEW(list)));
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, LIST_ITEM, str, -1);
在函數add_to_list() 中,我們利用系統函數gtk_tree_view_get_model()來獲得model。我們生成新的一行並把行中的數據交給model處理,這里正是借助 GtkTreeIter來完成這個功能。
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
GtkTreeSelection際上並不需要明確生成。在這里,我們是利用 GtkTreeView構件自動來生成。來幫助完成這項工作的正如你所見到的是系統函數gtk_tree_view_get_selection()。
g_signal_connect(selection, "changed",
G_CALLBACK(on_changed), label);
這個就很好理解了,把changed 信號與 GtkTreeSelection綁定,我們就可以與回調函數 on_changed()建立了聯系。
gtk_tree_model_get(model, &iter, LIST_ITEM, &value, -1); gtk_label_set_text(GTK_LABEL(label), value);
在這個回調函數里,我們取得了對應行的數據,當然是通過iter 來獲取的。
高級列表(Advanced List View)
在第二個例子中,我們將在前者的基礎上填加一些額外的功能。我們將實現能夠列表中填加或者去處其中的數據項。
#include <gtk/gtk.h>
enum
{
LIST_ITEM = 0,
N_COLUMNS
};
GtkWidget *list;
static void
append_item(GtkWidget *widget, gpointer entry)
{
GtkListStore *store;
GtkTreeIter iter;
const char *str = gtk_entry_get_text(entry);
store = GTK_LIST_STORE(gtk_tree_view_get_model(
GTK_TREE_VIEW(list)));
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, LIST_ITEM, str, -1);
}
static void
remove_item(GtkWidget *widget, gpointer selection)
{
GtkListStore *store;
GtkTreeModel *model;
GtkTreeIter iter;
store = GTK_LIST_STORE(gtk_tree_view_get_model(
GTK_TREE_VIEW (list)));
model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
if (gtk_tree_model_get_iter_first(model, &iter) == FALSE)
return;
if (gtk_tree_selection_get_selected(GTK_TREE_SELECTION(selection),
&model, &iter)) {
gtk_list_store_remove(store, &iter);
}
}
static void
remove_all(GtkWidget *widget, gpointer selection)
{
GtkListStore *store;
GtkTreeModel *model;
GtkTreeIter iter;
store = GTK_LIST_STORE(gtk_tree_view_get_model(
GTK_TREE_VIEW (list)));
model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
if (gtk_tree_model_get_iter_first(model, &iter) == FALSE)
return;
gtk_list_store_clear(store);
}
static void
init_list(GtkWidget *list)
{
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;
GtkListStore *store;
renderer = gtk_cell_renderer_text_new();
column = gtk_tree_view_column_new_with_attributes("List Item",
renderer, "text", LIST_ITEM, NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW (list), column);
store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING);
gtk_tree_view_set_model(GTK_TREE_VIEW (list),
GTK_TREE_MODEL(store));
g_object_unref(store);
}
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *sw;
GtkWidget *remove;
GtkWidget *add;
GtkWidget *removeAll;
GtkWidget *entry;
GtkWidget *vbox;
GtkWidget *hbox;
GtkTreeSelection *selection;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
sw = gtk_scrolled_window_new(NULL, NULL);
list = gtk_tree_view_new();
gtk_window_set_title (GTK_WINDOW (window), "List View");
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_container_set_border_width (GTK_CONTAINER (window), 10);
gtk_widget_set_size_request (window, 370, 270);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw),
GTK_SHADOW_ETCHED_IN);
gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (list), FALSE);
vbox = gtk_vbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 5);
hbox = gtk_hbox_new(TRUE, 5);
add = gtk_button_new_with_label("Add");
remove = gtk_button_new_with_label("Remove");
removeAll = gtk_button_new_with_label("Remove All");
entry = gtk_entry_new();
gtk_box_pack_start(GTK_BOX(hbox), add, FALSE, TRUE, 3);
gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, TRUE, 3);
gtk_box_pack_start(GTK_BOX(hbox), remove, FALSE, TRUE, 3);
gtk_box_pack_start(GTK_BOX(hbox), removeAll, FALSE, TRUE, 3);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 3);
gtk_container_add(GTK_CONTAINER (sw), list);
gtk_container_add(GTK_CONTAINER (window), vbox);
init_list(list);
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
g_signal_connect(G_OBJECT(add), "clicked",
G_CALLBACK(append_item), entry);
g_signal_connect(G_OBJECT(remove), "clicked",
G_CALLBACK(remove_item), selection);
g_signal_connect(G_OBJECT(removeAll), "clicked",
G_CALLBACK(remove_all), selection);
g_signal_connect (G_OBJECT (window), "destroy",
G_CALLBACK(gtk_main_quit), NULL);
gtk_widget_show_all(window);
gtk_main ();
return 0;
}
與前面的例子中的label不同的是,我們生成了三個按鈕和一個單行文本輸入框。我們將實現能夠動態的為列表增加一個新的數據項或者去處選中的數據項以及全部數據項。
sw = gtk_scrolled_window_new(NULL, NULL);
...
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw),
GTK_SHADOW_ETCHED_IN);
...
gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 5);
...
gtk_container_add(GTK_CONTAINER (sw), list);
GtkTreeView構件被放置在帶有滑塊的窗口中。
if (gtk_tree_selection_get_selected(GTK_TREE_SELECTION(selection),
&model, &iter)) {
gtk_list_store_remove(store, &iter);
}
系統函數 gtk_list_store_remove()的功能是去處列表中的所選的數據項。
gtk_list_store_clear(store);
系統函數gtk_list_store_clear()將用於清除列表中的所有數據項。
if (gtk_tree_model_get_iter_first(model, &iter) == FALSE)
return;
上面的代碼是用於檢查是否在列表中還存有剩下的數據項。很顯然,我們能夠把列表清除的一干二凈。
樹狀視圖(Tree View)
接着,我們將向大家展示如何運用構件GtkTreeView來去顯示有等級差異的數據項。在先前的兩個例子中,我們我們用到了列表試圖,現在我們介紹樹狀視圖。
#include <gtk/gtk.h>
enum
{
COLUMN = 0,
NUM_COLS
} ;
void on_changed(GtkWidget *widget, gpointer statusbar)
{
GtkTreeIter iter;
GtkTreeModel *model;
char *value;
if (gtk_tree_selection_get_selected(
GTK_TREE_SELECTION(widget), &model, &iter)) {
gtk_tree_model_get(model, &iter, COLUMN, &value, -1);
gtk_statusbar_push(GTK_STATUSBAR(statusbar),
gtk_statusbar_get_context_id(GTK_STATUSBAR(statusbar),
value), value);
g_free(value);
}
}
static GtkTreeModel *
create_and_fill_model (void)
{
GtkTreeStore *treestore;
GtkTreeIter toplevel, child;
treestore = gtk_tree_store_new(NUM_COLS,
G_TYPE_STRING);
gtk_tree_store_append(treestore, &toplevel, NULL);
gtk_tree_store_set(treestore, &toplevel,
COLUMN, "Scripting languages",
-1);
gtk_tree_store_append(treestore, &child, &toplevel);
gtk_tree_store_set(treestore, &child,
COLUMN, "Python",
-1);
gtk_tree_store_append(treestore, &child, &toplevel);
gtk_tree_store_set(treestore, &child,
COLUMN, "Perl",
-1);
gtk_tree_store_append(treestore, &child, &toplevel);
gtk_tree_store_set(treestore, &child,
COLUMN, "PHP",
-1);
gtk_tree_store_append(treestore, &toplevel, NULL);
gtk_tree_store_set(treestore, &toplevel,
COLUMN, "Compiled languages",
-1);
gtk_tree_store_append(treestore, &child, &toplevel);
gtk_tree_store_set(treestore, &child,
COLUMN, "C",
-1);
gtk_tree_store_append(treestore, &child, &toplevel);
gtk_tree_store_set(treestore, &child,
COLUMN, "C++",
-1);
gtk_tree_store_append(treestore, &child, &toplevel);
gtk_tree_store_set(treestore, &child,
COLUMN, "Java",
-1);
return GTK_TREE_MODEL(treestore);
}
static GtkWidget *
create_view_and_model (void)
{
GtkTreeViewColumn *col;
GtkCellRenderer *renderer;
GtkWidget *view;
GtkTreeModel *model;
view = gtk_tree_view_new();
col = gtk_tree_view_column_new();
gtk_tree_view_column_set_title(col, "Programming languages");
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
renderer = gtk_cell_renderer_text_new();
gtk_tree_view_column_pack_start(col, renderer, TRUE);
gtk_tree_view_column_add_attribute(col, renderer,
"text", COLUMN);
model = create_and_fill_model();
gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);
g_object_unref(model);
return view;
}
int
main (int argc, char **argv)
{
GtkWidget *window;
GtkWidget *view;
GtkTreeSelection *selection;
GtkWidget *vbox;
GtkWidget *statusbar;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_title(GTK_WINDOW(window), "Tree View");
gtk_widget_set_size_request (window, 350, 300);
vbox = gtk_vbox_new(FALSE, 2);
gtk_container_add(GTK_CONTAINER(window), vbox);
view = create_view_and_model();
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
gtk_box_pack_start(GTK_BOX(vbox), view, TRUE, TRUE, 1);
statusbar = gtk_statusbar_new();
gtk_box_pack_start(GTK_BOX(vbox), statusbar, FALSE, TRUE, 1);
g_signal_connect(selection, "changed",
G_CALLBACK(on_changed), statusbar);
g_signal_connect (G_OBJECT (window), "destroy",
G_CALLBACK(gtk_main_quit), NULL);
gtk_widget_show_all(window);
gtk_main();
return 0;
}
在我們上面的示例中,我們來完成一項任務:把腳本語言和傳統編程語言對應的數據項,進行區分。“語言種類”作為其對應數據項中的頂層節點,也就是說是一行數據列表的“頭頭”。當前選種的數據項,將在狀態欄中顯示出來。
從上面的這些步驟中,我們可以清晰的看到,樹狀視圖與列表視圖的生成方法很相似。
GtkTreeStore *treestore;
這里我們當然要使用一個不同的model—— GtkTreeStore。
treestore = gtk_tree_store_new(NUM_COLS,
G_TYPE_STRING);
我們生成的 GtkTreeStore只有一列。
gtk_tree_store_append(treestore, &toplevel, NULL);
gtk_tree_store_set(treestore, &toplevel,
COLUMN, "Scripting languages",
-1);
這其中的代碼就是在完成一個頂層節點的操作。
gtk_tree_store_append(treestore, &child, &toplevel);
gtk_tree_store_set(treestore, &child,
COLUMN, "Python",
-1);
上面的代碼在生成一個子數據項。
