renzhenmjr 2009-11-17
想要使用GtkTreeView實在不是一件"簡單"的事。我在這把簡單特意括了起來,是因為要提醒您一下。我並不是想要暗示您聯想到他是很難的,在這裡我選擇了另一種相對的意義 -- 繁複。步驟多了一點,但概念上並不算難以理解。也許您已經領教過落落長的GTK+ 2.0 Tree View Tutorial(Tim-Philipp Mler, 2005)作者是希望他能涵蓋大部分的主題,所以篇幅與細微的程度當然是有所要求的。不過每個讀者都篇好不同的風格,弟就試著寫一篇具體而微的短篇試試。
在吸收GTK+ 2.0 Tree View Tutorial的同時,也複習了一下MVC這一個複合式的designpattern。反覆思考著,學習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狀態的元件卻沒改變就變成了dirtydata,當然你也可以選則在更動的同時更新顯示狀態的元件。但是這樣並不理想,萬一這些元件的值需要與其他未更動的資料交互運算,這樣程式的複雜性就增加了。GUI(View)與Model較緊密地結合在一起,實在不是一個理想的設計。
在這裡,需要知道Model是否被改變了,而View要也為改變做出反應。前人們就思考著除了不斷地在背景查詢Model是不是真的改變了,再來更新 View這種笨拙的方式時。想出了另一種設計思維"Don't Call Me, I'will CallYou"[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來協助。
使用GtkTreeView上的手續也許有點繁複,但也就是那幾件事為View建立GtkTreeView、GtkTreeViewColumn、GtkCellRenderer;為Model建立GtkListStore或GtkTreeStore。最後,用gtk_tree_view_set_model讓他們相連在一起。
#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;}在這裡我們示範如何使用GtkListStore這個GtkTreeModel的子類別,GtkTreeStore的用法也大同小異,只不過他包含了樹狀這一種的階層關係,所以在資料上的檢索方式不太一樣,所以對Iterator的走訪方式也設計了不同的用法。
#include <gtk/gtk.h> #include <glib.h>
enum{
col_name=0,
col_date,
col_size,
n_cols
};
voidmodel_data_new(GtkTreeModel*store,
constgchar*name,constgchar*date,constguintsize){
GtkTreeIteriter;
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);
returnGTK_TREE_MODEL(store);
}intmain(intargc,char*argv[]){/*GtkWidgetisthestoragetypeforwidgets*/GtkWidget*window;/*ThisiscalledinallGTKapplications.Argumentsareparsed*fromthecommandlineandarereturnedtotheapplication.*/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);/*AllGTKapplicationsmusthaveagtk_main().Controlendshere*andwaitsforaneventtooccur(likeakeypressor*mouseevent).*/gtk_main();return0;}
GtkCellRenderer
#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;
//col1: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);
//col2:date
gtk_tree_view_insert_column_with_attributes(
GTK_TREE_VIEW(view),-1,"date",renderer,"text",col_date,NULL);
//col3:size
gtk_tree_view_insert_column_with_attributes(
GTK_TREE_VIEW(view),-1,"size",renderer,"text",col_size,NULL);
}
intmain(intargc,char*argv[]){/*GtkWidgetisthestoragetypeforwidgets*/GtkWidget*window;/*ThisiscalledinallGTKapplications.Argumentsareparsed*fromthecommandlineandarereturnedtotheapplication.*/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);//arrangeviewcolumns
arrange_tree_view(view);
//setmodel
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;}雖然文件只寫到這裡,但是才是您Gtk Tree View學習的起點。在這裡只是先讓您撇開複雜的部分,先體驗一下使用上的workflow。先掌握了流程,再針對各流程的細節去了解。而不是一開始就挖向細微的部分,見樹不見林。從此怕害而停滯不前:)