想要使用GtkTreeView實在不是一件"簡單"的事。我在這把簡單特意括了起來,是因為要提醒您一下。我並不是想要暗示您聯想到他是很難的,在這裡我選擇了另一種相對的意義 -- 繁複。步驟多了一點,但概念上並不算難以理解。也許您已經領教過落落長的GTK+ 2.0 Tree View Tutorial(Tim-Philipp Mler, 2005)作者是希望他能涵蓋大部分的主題,所以篇幅與細微的程度當然是有所要求的。不過每個讀者都篇好不同的風格,弟就試著寫一篇具體而微的短篇試試。
在吸收GTK+ 2.0 Tree View Tutorial的同時,也複習了一下MVC這一個複合式的design pattern。反覆思考著,學習GtkTreeView的使用,真的需要有MVC的概念嗎? 雖然Gtk這一個GUI的library內一定用了許MVC的設計思維,但是對於使用者來說我們不一定明白MVC著力於那些地方,一直拿出來強調反而使人困惑。但當你要學習使用GtkTreeView時,您就不能夠再將MVC視而不見了,因為這是一個MVC的半成品。就像在玩猜字遊戲般,您要在對的格子填上有用的資訊,縱橫交錯之下才能使整個遊戲完美了起來。
在這裡,我不打算深入解釋MVC。但是要先為MVC這三個字母,做一下"狹義"的定義;M - Model,你可以把他當成"資料",並且有一組專用的函式負責操作(增加/刪除/排序/查詢等功能)這些"資料";V - View,在Gtk中你可以想像成Widgets,任何可以把"資料"顯示給終於使用者的東西,都可以稱為View。C - Controller,這裡採用比較不精確的講法 -- 協調者。負著協調View與Model應用的反應。
稍作名詞定義了之後,我們來稍懂一下MVC的互動模式。這有點像三角關係,但又如同編劇為各個角色設定了令人扼腕的個性。View總是優柔寡斷沒有自己的意見把Model的想法當成是自己的想法,Model總是自我中心要整個世界跟他著轉動,Controller是唯一讓Model信任的朋友,Model 只肯為了Controller做出改變,而Controller與View的關係也是唯妙的,View是唯一能讓Controller做點什麼的人。他們一個看著一個的背影,視線只在二者之間。
故事聽完之後,我們回到嚴肅一點的情境。Model也就是程式所要操弄的資料,沒有資料程式就不具有存在的意義。但是我們要寫GUI程式,左一個 button右一個button很容易不小心就觸動了什麼,萬一這一個觸發可以直接改變Model,但是GUI上對應Model狀態的元件卻沒改變就變成了dirty data,當然你也可以選則在更動的同時更新顯示狀態的元件。但是這樣並不理想,萬一這些元件的值需要與其他未更動的資料交互運算,這樣程式的複雜性就增加了。GUI(View)與Model較緊密地結合在一起,實在不是一個理想的設計。
在這裡,需要知道Model是否被改變了,而View要也為改變做出反應。前人們就思考著除了不斷地在背景查詢Model是不是真的改變了,再來更新 View這種笨拙的方式時。想出了另一種設計思維"Don't Call Me, I'will Call You"[1]。Model對View說,別找我,有事我會找你。這樣主動的角色就調換了,讓Model主動通知View,他的狀態已經有所改變了。對 View來說,他自從不主動之後。生活上有點改變了。變得悠閒了,沒事不會去找事做。為了這樣的改變提供公用的update函式,並且把自己登記在 Model的通知名冊之上,讓Model在狀態改變時可以通知他update。(M與V)
剛剛提到了"資料被改變",回頭想想改變的起點。不就是做在電腦前的各位使用者嗎?你正享受著GUI程式,上面也許有許多按鈕,也許有地方讓你寫點什麼抒發一下情感。這任何一個動作都可能造成Model有所改變。但是以MVC的思考模式,View並不會直接改變Model,而是向Controller請求改變,透過Controller去改變Model。而Controller通常是一組對應View中所提供的功能的函式,代表著View中應有的行為。當然有些行為並不會改變Model。(V與C)
在前一個段落介紹了點MVC的概念,實在得承認這是很偷懶的介紹方式。MVC實在是一個很大的議題啊! 暫且先隨我"短視"一下,我們來看一下GtkTreeView、GtkTreeViewColumn、GtkTreeModel、 GtkCellRenderer、GtkTreeIter分別代表MVC的那些部分。先來看一下下面這一張"簡化"的類別圖。GtkTreeView是整個Widget的門面,只有GtkTreeView並不能真的讓我們的程式有用,還需要GtkTreeModel的協助才能夠持有資料。而每一種資料的呈現方式也不盡相同,所以還需要GtkCellRenderer來協助。
#include <gtk/gtk.h>
#include <glib.h>
int main( int argc,
char *argv[] ) {
/* GtkWidget is the storage type for widgets */
GtkWidget * window;
/* This is called in all GTK applications. Arguments are parsed
* from the command line and are returned to the application. */
gtk_init ( &argc, &argv );
window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
gtk_window_set_title( GTK_WINDOW( window ), "Tree");
g_signal_connect( G_OBJECT( window ), "destroy", gtk_main_quit, NULL);
gtk_widget_show_all ( window );
/* All GTK applications must have a gtk_main(). Control ends here
* and waits for an event to occur (like a key press or
* mouse event). */
gtk_main ();
return 0;
}
#include <gtk/gtk.h>
#include <glib.h>
int main( int argc,
char *argv[] ) {
/* GtkWidget is the storage type for widgets */
GtkWidget * window;
/* This is called in all GTK applications. Arguments are parsed
* from the command line and are returned to the application. */
gtk_init ( &argc, &argv );
window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
gtk_window_set_title( GTK_WINDOW( window ), "Tree");
g_signal_connect( G_OBJECT( window ), "destroy", gtk_main_quit, NULL);
GtkWidget * view;
view = gtk_tree_view_new();
gtk_container_add( GTK_CONTAINER(window), view);
gtk_widget_show_all ( window );
/* All GTK applications must have a gtk_main(). Control ends here
* and waits for an event to occur (like a key press or
* mouse event). */
gtk_main ();
return 0;
}
#include <gtk/gtk.h>
#include <glib.h>
enum{
col_name = 0,
col_date,
col_size,
n_cols
};
void model_data_new(GtkTreeModel* store,
const gchar* name, const gchar* date, const guint size) {
GtkTreeIter iter;
gtk_list_store_append(GTK_LIST_STORE(store), &iter);
gtk_list_store_set(GTK_LIST_STORE(store), &iter,
col_name, name,
col_date, date,
col_size, size,
-1);
}
GtkTreeModel* create_model() {
GtkListStore *store;
store = gtk_list_store_new (n_cols,
G_TYPE_STRING,G_TYPE_STRING,G_TYPE_UINT);
return GTK_TREE_MODEL(store);
}
int main( int argc,
char *argv[] ) {
/* GtkWidget is the storage type for widgets */
GtkWidget * window;
/* This is called in all GTK applications. Arguments are parsed
* from the command line and are returned to the application. */
gtk_init ( &argc, &argv );
window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
gtk_window_set_title( GTK_WINDOW( window ), "Tree");
g_signal_connect( G_OBJECT( window ), "destroy", gtk_main_quit, NULL);
GtkWidget * view;
view = gtk_tree_view_new();
gtk_container_add( GTK_CONTAINER(window), view);
gtk_widget_show_all ( window );
/* All GTK applications must have a gtk_main(). Control ends here
* and waits for an event to occur (like a key press or
* mouse event). */
gtk_main ();
return 0;
}
#include <gtk/gtk.h>
#include <glib.h>
enum{
col_name = 0,
col_date,
col_size,
n_cols
};
void model_data_new(GtkTreeModel* store,
const gchar* name, const gchar* date, const guint size) {
GtkTreeIter iter;
gtk_list_store_append(GTK_LIST_STORE(store), &iter);
gtk_list_store_set(GTK_LIST_STORE(store), &iter,
col_name, name,
col_date, date,
col_size, size,
-1);
}
GtkTreeModel* create_model() {
GtkListStore *store;
store = gtk_list_store_new (n_cols,
G_TYPE_STRING,G_TYPE_STRING,G_TYPE_UINT);
return GTK_TREE_MODEL(store);
}
void arrange_tree_view(GtkWidget* view) {
GtkCellRenderer* renderer;
// col 1: name
renderer = gtk_cell_renderer_text_new ();
gtk_tree_view_insert_column_with_attributes(
GTK_TREE_VIEW(view), -1, "name", renderer, "text", col_name, NULL);
// col 2: date
gtk_tree_view_insert_column_with_attributes(
GTK_TREE_VIEW(view), -1, "date", renderer, "text", col_date, NULL);
// col 3: size
gtk_tree_view_insert_column_with_attributes(
GTK_TREE_VIEW(view), -1, "size", renderer, "text", col_size, NULL);
}
int main( int argc,
char *argv[] ) {
/* GtkWidget is the storage type for widgets */
GtkWidget * window;
/* This is called in all GTK applications. Arguments are parsed
* from the command line and are returned to the application. */
gtk_init ( &argc, &argv );
window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
gtk_window_set_title( GTK_WINDOW( window ), "test");
g_signal_connect( G_OBJECT( window ), "destroy", gtk_main_quit, NULL);
GtkWidget* view;
view = gtk_tree_view_new();
gtk_container_add( GTK_CONTAINER(window), view);
// arrange view columns
arrange_tree_view(view);
// set model
GtkTreeModel* store = create_model();
gtk_tree_view_set_model ( GTK_TREE_VIEW(view), store);
model_data_new(store, "test.c", "2006-07-29", 2224);
model_data_new(store, "xd.c", "2006-07-29", 454);
g_object_unref( store );
gtk_widget_show_all ( window );
/* All GTK applications must have a gtk_main(). Control ends here
* and waits for an event to occur (like a key press or
* mouse event). */
gtk_main ();
return 0;
}