【@.1 MVVM設計模式與Glade】
做上層軟件開發的程序員可能對於MVVM模式比較熟悉,這是一種經典的軟件設計模式,很好的將用戶界面與后台處理之間分層開,通過屬性、事件綁定這種統一的"接口"將軟件重新組裝起來,將原本看上去很混亂很冗余的軟件開發流程抽象出來,以一種統一而又合理的思想來組織軟件開發。下面截自wiki的一幅圖簡單說明了MVVM模式的組織結構。
View層提供了人機交互界面,Model層則是處理實際邏輯操作、數據操作的核心,二者之間由ViewModel層來進行協調,即綁定(Binding)View層的操作和屬性,請求Model執行並得到反饋結果。
MVVM模式在Windows平台下的WPF開發得到了很好的體現,而WPF開發中View層並不是用C代碼,而是用XAML來描述的,這是以前的傳統軟件開發或是MFC開發中所沒有的。在實際使用這種WPF開發時可以用Expression Blend來進行UI設計,這個軟件專門而且僅僅生成一個XAML文件用於界面描述,而底層的邏輯代碼直接連到Visual Studio中進行編寫。通過Blend畫出的界面可以很好的釋放你的創造力想象力,而其跟Visual Studio之間的無縫連接將MVVM模式演繹的淋漓盡致。因此我開始考慮有沒有一種更通用的工具,不僅僅局限於Windows平台,能在多數平台下用MVVM模式進行開發。於是我發現了Glade與GTK的配合。
GTK是一個以純C語言開發的圖形庫,同樣適用於跨平台開發中。對於做底層開發比較多的人來說看到純C代碼比較親切,我也一直想用純C,而不是Visual Studio中提倡的C#,進行軟件開發。單獨使用GTK非常棒,但是一個問題就在於,需要手寫的代碼太多了。畫一個按鍵,按鍵的布局,按鍵的事件,這些都需要自己一行行手寫,而且還得留意代碼的順序。而其中一個我覺得是很大的門檻在於,布局需要盲打,即,你只能先在腦中有個界面的想法,寫好代碼后才能看到界面的結果。當然對於嵌入式設計中的很多界面設計都是盲打,比如uCGUI,但是如果有一個工具能像Visual Studio之類的集成開發環境一樣能畫界面,再生成代碼,那開發的速度將大大提升。Glade就很好的解決了這一問題。
Glade很像Expression Blend的作用,它僅僅生成一個xml文件,描述了界面是怎樣布局的,界面上需要綁定那些事件(Signal)。通過Glade+GTK開發的程序每次在運行時都會解析這個xml文件生成一個界面(是不是跟WPF一樣~),main函數里的代碼量大大減少,所需要的就是新建一個builder,解析這個xml,傳給gtk中的窗口類型,再顯示這個窗口,之后進入Gtk的主循環即可。
但是,Glade+GTK劣勢於Blend的一個關鍵特性是,不支持屬性綁定。雖然GTK中的Glib在2.26以后就增加了GBinding,也就是屬性綁定的支持,但是在Glade中卻並沒體現出屬性綁定的設置,至少不能像Blend一樣,每一個屬性都可以方便的設置綁定。在C代碼中手動編寫代碼來進行綁定或許可以實現(有待測試),但是官方教程上並沒有這一特性介紹,也沒有像Blend中那樣強調屬性綁定的重要性。這一特性的缺失導致Glade+GTK實現的MVVM有所殘缺。這篇文章的作者在進行向Glade和GTK中增加Binding的實現,可以參考他對於binding的描述。不知道現在進展如何了。
不過話說回來,就算沒有屬性綁定也能寫出一個好的界面出來不是?
【@.2 軟件准備】
開發時所需的工具很簡單,下面分開說明(也可以參考文章末尾打包好的所有模塊下載):
1.Gtk。由於Gtk是跨平台的圖形界面庫,因此對於windows下的Gtk不見得是最新版本的,但是其功能也足夠我們使用了。在這個頁面中可以找到Windows平台下的Gtk庫,我下載的時候windows版本的gtk只有2.24版本。除開源代碼,每個模塊有兩個下載分支,一個是Dev也就是開發時所需庫和頭文件,另一個是Run-time的動態鏈接庫。編譯好的Gtk程序僅需將run-time的庫解壓到運行程序相同文件夾(或者可自己配置),即可將程序發布。如果覺得一個個模塊下載麻煩,可以下載all-in-one bundle,包含了Run-time和Dev版本。下載后解壓到任意位置即可,比如c:\gtk\下,編譯時需要向環境變量中的PATH添加c:\gtk\bin就可以了。
2.Glade。在官方網站可以下載到windows平台下的glade。需注意下載的glade版本與gtk版本是否匹配,我下載時glade3.8.x版本支持gtk 2版本,3.14.x支持gtk 3版本。由於前面下的gtk版本是2.24,這里下載glade需3.8.3版本。
3.需要一個在windows下的GNU編譯器,也就是MinGW。我在這篇博客中詳細介紹了Windows下配置MinGW的方法,同樣也有配置完整的打包下載,可以參考自行配置這里不再累述。
4.一個文本編輯器。系統自帶的文本編輯器就可以了,不過為了編輯代碼方便,windows下我還是推薦Notepad++來編輯,請自行google下載最新版本即可。當然為了有一個完整的IDE,可以使用Eclipse配置一個完善的開發環境,不過這里為了保持問題的簡潔性暫且不考慮采用Eclipse。
【@.3 程序實例】
保證所有軟件下載安裝完成,即
Gtk解壓,比如解壓到C:\gtk\。
Glade安裝結束。
MinGW配置可用。如果你采用前面提到我自行配置的MinGW注意僅需解壓即可,比如解壓到C:\MinGW\
關於Glade的教程,可以參考下面鏈接:
GTK+ and Glade3 GUI Programming Tutorial
但是需注意的是,這篇教程中的源代碼在Windows下編譯后不能很好的運行(也就是所有的事件不能正常綁定),並且其中的Glade版本比較低,用Glade生成的界面文件與我們的主程序代碼聯系在一起的步驟有變,所以僅僅參考其Glade的界面設計步驟即可。
打開glade,在左側的Toplevels中新建一個Window,右側的屬性中將name改為mainWindow。一般在GTK設計中需要新增一個Container來進行布局的調整,這里為了演示方便,就簡單的拖動一個按鈕在界面上就好了。將按鈕名字改為button_Click。
下面新建兩個事件(Signal)。在mainWindow中右側屬性欄切換到Signals一欄,找到GtkObject下的destroy,在Handler下選擇on_mainWindow_destroy,這將作為這個控件銷毀,也就是窗口關閉時的發出的信號。
另外一個按鍵的類似,切換到右側屬性欄的Signals,找到GtkButton中的clicked,選擇Handler為on_button_Click_clicked。這個作為點擊按鈕發送的信號。
保存這個界面設計為,比如Tutor1.glade,在任意目錄下。
在同一目錄下新建一個main.c文件,輸入如下代碼
#include <gtk/gtk.h> //Add G_MODULE_EXPORT to signal function prototype is important in Windows!!! //#define G_MODULE_EXPORT __declspec(dllexport) static unsigned int clkcount; G_MODULE_EXPORT void on_mainWindow_destroy(GtkObject *object, gpointer user_data) { g_print("Quit Here!"); gtk_main_quit(); } //G_MODULE_EXPORT must be add!! G_MODULE_EXPORT void on_button_Click_clicked(GtkObject *object, gpointer user_data) { clkcount++; g_print(" **Button Clicked!** %d\n",clkcount); } int main (int argc, char *argv[]) { GtkBuilder *builder; GtkWidget *window; // clkcount=0; // gtk_init(&argc, &argv); builder = gtk_builder_new(); gtk_builder_add_from_file(builder, "Tutor1.glade", NULL); // window = GTK_WIDGET(gtk_builder_get_object(builder, "mainWindow")); //add the top window in the glade code gtk_builder_connect_signals(builder, NULL); g_object_unref(G_OBJECT(builder)); // gtk_widget_show(window); gtk_main(); return 0; }
這既是運行一個Glade+GTK設計出的界面程序的最小代碼,可以看到主函數里面的代碼量相比於單純用Gtk來編寫少了許多。
注意到前面新建的兩個信號,on_mainWindow_destroy和on_button_Click_clicked前面都加了G_MODULE_EXPORT進行修飾。這是一個宏,在代碼注釋中寫出了這個宏的展開。如果不加它,則最后編譯出的運行程序將會找不到對應的事件(handler)。之后需要編譯。同文件夾下新建一個build.bat,輸入如下代碼(假設gtk安裝在c:\gtk\bin下,並且MinGW的路徑為c:\MinGW\bin)
@echo off
::Set GTK for compile and runtime libs
set PATH=C:\gtk\bin;%PATH%
set PATH=c:\MinGW\bin;%PATH%
(pkg-config --cflags --libs gtk+-2.0 )>temp
set /p pkg=<temp
del temp
::gcc -Wall -mwindows -g -o main main.c %pkg%
gcc -Wall -g -o main main.c %pkg%
編譯成功后,如果不在同文件夾下放置所有的運行庫,則程序將無法運行。這時候最簡單的就是將所有運行庫dll解壓到同文件夾下即可運行(文章末尾打包文件中有),另一種方法是寫一個腳本設置運行庫的路徑之后再運行程序。可以新建一個launch.bat,輸入如下代碼
@echo off
set PATH=c:\gtk\bin;%PATH%
start main.exe
即可運行。
下面是所用到程序的打包,其中包含了例程以及必要腳本