下面的代碼是筆者用GTK+編寫的一個HelloWorld例程,編譯後運行顯示一個帶按鈕的窗口,點擊按鈕會彈出提示信息對話框。
//hello.c #include <gtk/gtk.h> //主窗口中按鈕的回調函數 void on_button_clicked(GtkWidget* button, gpointer userdata) { GtkWidget *dialog; //創建帶確認按鈕的對話框,父控件爲空 dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL |GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, (gchar*)userdata); gtk_dialog_run(GTK_DIALOG(dialog));//顯示並運行對話框 gtk_widget_destroy(dialog);//銷燬對話框 } //主函數 int main(int argc, char* argv[]) { GtkWidget *window, *button; //初始化GTK+程序 gtk_init(&argc, &argv); //創建窗口,併爲窗口的關閉信號加回調函數以便退出 window = gtk_window_new(GTK_WINDOW_TOPLEVEL); g_signal_connect(G_OBJECT(window),"delete_event", G_CALLBACK(gtk_main_quit),NULL); gtk_window_set_title(GTK_WINDOW(window),"Hello World!"); gtk_container_set_border_width(GTK_CONTAINER(window),10); //創建按鈕控件,爲單擊信號加回調函數,將其放入窗口中 button=gtk_button_new_with_label("Hello World!"); g_signal_connect(G_OBJECT(button),"clicked", G_CALLBACK(on_button_clicked),(gpointer)"你好!\n自由的世界。"); gtk_container_add(GTK_CONTAINER(window),button); //下面函數顯示窗口控件同時顯示其中的所有其它控件 gtk_widget_show_all(window); gtk_main(); return FALSE; } |
可以直接用命令"gcc `pkg-config -cflags -libs gtk+2.0` hello.c -o hello"來編譯上面的代碼,但最好做一個Makefile文件內容如下:
CC = gcc all: $(CC) `pkg-config --cflags --libs gtk+-2.0` hello.c -o hello |
這樣的話可以用make命令來編譯,使得簡單了許多,也不容易出錯了。再次強調的是關於引號的問題,很多初學者常犯這個錯誤,[`]是[~]下面的那個單引號,而非['];這涉及到了LINUX SHELL編程中的命令引用,LINUX下的標準的BASH是支持命令引用的,而其它的SHELL就不一定了。
下兩圖分別爲程序的運行時的窗口和點擊Hello World按鈕彈出的對話框:
與MS WINDOWS下用C開發GUI程序不同,GTK+不用WinMain函數,由C語言中的標準格式的main函數直接切入,這在UNIX操作系統家族中是統一的。函數gtk_init標緻GTK+程序的開始,它的兩個參數是main函數的兩個參數的地址。在此函數之後就可以處理程序的各種相關部分,如控件的創建、顯示、爲控件的信號加回調函數、設定或修改控件的屬性等。最後執行gtk_main函數,程序進入主事件循環,開始接收信號併爲信號調用其相應用的回調函數。函數gtk_main_quit用來結束主事件循環,即退出GTK+程序的運行。
GTK+中的控件分爲容器控件和非容器控件1,非容器控件主要是基礎的GUI元素,如文字標籤、圖像、文字錄入控件等,容器控件有多種,共同點是可以按一定方式來排放其它控件,GTK+以此形成了獨特的GUI界面佈局風格。GTK+控件的創建函數一般形式爲:gtk_控件名_new(參數…)或gtk_控件名_new_with_參數名(參數…),它的返回值爲GtkWidget型的指針,創建完成後就可以調用gtk_widget_show函數來顯示或隱藏此控件,或用相關的函數來修改控件的屬性。
GTK+用信號和回調函數的方式來處理來自外部的事件,控件間繼承有其父控件的相同信號,不同的控件也有各自不同的信號,如按鈕控件有"clicked"信號,而文字標籤控件則沒有此信號。GTK+2.0採用宏g_signal_connect來完成信號與回調函數的連接,這是它與GTK+1.X版的一個關鍵不同之處,這個宏有四個參數,第一個參數是連接信號的對象,如此例中的button或window,要用G_OBJECT宏來轉換一下,即將對象的類型GtkWidget轉換爲GObject類型,格式一般爲G_OBJECT(button);第二個參數爲字符串格式的信號名;第三個參數爲回調函數名,用G_CALLBACK宏來轉換一下;第四個參數爲要傳給回調函數的參數的指針。如上例中爲按鈕的"clicked"的信號加的回調函數"on_button_clicked":
g_signal_connect(G_OBJECT(button),"clicked", G_CALLBACK(on_button_clicked),(gpointer)"你好!\n自由的世界。"); |
細心的讀者馬上會看到此宏的一個缺點,即只能爲回調函數傳遞一個參數,當然聰明的讀者馬上也會想到採用結構類型來傳遞多個參數。上面的內容對初學者似乎複雜了些,只要過了這個門檻,事實上就步入了GTK+的世界。
上面的程序運行是主窗口顯示爲英文,我們完全可以將其改爲中文,這樣單一的語言版本不適於應用的國際化,GTK+中用gettext軟件包來實現國際化,使這一問題變得非常簡單。gettext軟件包是GNU工程中解決國際化問題的重要工具,目前版本是0.11.x,支持C/C++和JAVA語言,它在開源界應用相當廣泛,GNOME/GTK+的國際化問題都是用它來解決的,正常的情況下GNU/LINUX系統是默認安裝這一軟件包的。
首先是在源代碼中加入相關的C語言頭文件如下:
#include <libintl.h> //gettext支持 #include <locale.h> //locale支持 |
然後是定義宏,下面的定義形式在GNOME/GTK+中應用的標準格式:
#define PACKAGE "hello" //軟件包名 #define LOCALEDIR "./locale" //locale所在目錄 #define _(string) gettext(string) #define N_(string) string |
在程序的主函數中加入下面相關函數:
bindtextdomain(PACKAGE,LOCALEDIR); //以上函數用來設定國際化翻譯包所在位置 textdomain(PACKAGE); //以上函數用來設定國際化翻譯包名稱,省略了.mo |
將代碼中需要國際化--即多語言輸出的字符串改寫爲_()宏,代碼如下:
gtk_window_set_title(GTK_WINDOW(window),_("Hello World!")); ... ... button=gtk_button_new_with_label(_("Hello World!")); g_signal_connect(G_OBJECT(button),"clicked", G_CALLBACK(on_button_clicked),(gpointer)(_("Hello, the Free World!"))); ... ... |
完成以上修改後,執行如下命令:xgettext -k_ -o hello.po hello.c,它的功能是將hello.c中的以下劃線開始括號中(如宏定義所示)的字符串加入到hello.po文件中。po文件的頭部可以加入軟件包的名稱、版本、翻譯者的郵件地址等,po文件中以#開始的行爲註釋內容,以下爲省略了頭部的hello.po文件內容,msgid後面的內容爲英文,msgstr後面的內容爲翻譯的中文,翻譯好後保存爲UTF8格式。
#: hello.c:26 hello.c:29 msgid "Hello World!" msgstr "你好世界!" #: hello.c:31 msgid "Hello, the Free World!" msgstr "你好,自由的世界!" |
下一步執行命令:msgfmt -o hello.mo hello.po將 hello.po文件格式化爲hello.mo文件,這樣程序在運行時就能根據當前locale的設定來正確讀取mo文件中的數據,從而顯示關語言的信息了。關於.mo文件的位置,本程序設在./locale目錄下的中文目錄zh_CN下的LC_MESSAGES目錄下,即./locale/zh_CN/LC_MESSAGES 目錄下,在REDHAT中默認的目錄是/usr/share/locale。將此步驟生成的mo文件複製到相應的目錄下,將locale設爲簡體中文,再運行此程序,測試結果就變爲中文了(如下圖),如locale設爲英文則顯示仍爲上面的英文信息。
自動生成Makefile文件是很多LINUX編程愛好者的願望,事實上只要你運用好AUTOCONF和AUTOMAKE這兩個工具,就會很容易的生成Makefile文件,並能實現打包(生成*.tar.gz格式的源代碼包)功能。與這兩個工具相關的配置文件分別是configure.in和Makefile.am。只要我們弄懂這兩個文件的格式和相關的宏,就完全可以了。
前提是做好相應的目錄和源程序文件,如本例中在hello目錄中的hello.c等。
1、首先執行autoscan命令,生成configure.scan文件;
2、執行mv configure.scan configure.in,將其改名;
3、編輯configure.in,在AC_INIT(hello.c)之後加入一行,AM_INIT_AUTOMAKE(hello,1.0)表示軟件包名爲hello,版本爲1.0,如此在make編譯後,執行make dist會生成一個名爲 hello-1.0.tar.gz源代碼包,這樣是比較附合GNU開源標準的格式。在最後一行AC_OUTPUT()的括號中加入Makefile,表示輸出Makefile文件(configure.in文件中還有許多宏定義,詳細用法可以參考autobook一書),至此編輯configure.in文件結束;
4、執行aclocal命令,生成aclocal.m4宏文件;
5、執行autoconf命令,生成configure shell可執行腳本;
6、編輯Makefile.am文件,內容如下:
AUTOMAKE_OPTIONS = foreign INCLUDES = `pkg-config --cflags gtk+-2.0` LIBS = `pkg-config --libs gtk+-2.0` bin_PROGRAMS = hello hello_SOURCES = hello.c |
說明:第一行爲AUTOMAKE命令的參數,表示爲外部的,不按GNU標準(即不加說明、安裝、更改記錄等文件) ;第二行表示包含文件的目錄;第三行表示動態鏈接庫的目錄和所要鏈接的庫;第四行表示輸出的可執行文件名;最後一行表示可執行文件的源程序文件,可以有多個文件名。
7、執行命令automake --add-missing -copy,表示創建Makefile.in文件並加入遺失的文件,同時複製過來(默認情況下是做符號鏈接,這在不同的文件系統間會出問題),至此操作完成。
執行./configure 生成Makefile;執行make編譯;執行./hello運行此程序;執行make install來安裝,默認情況下是將可執行文件hello複製到/usr/local/bin目錄下;執行make dist會在當前目錄下生成hello-1.0.tar.gz源代碼包,如此就可以將你的源代碼包向外界發佈了。
在GTK+中應用線程, 除了GLIB中的g_thread_init和g_thread_supported兩個函數外,還要用到gdk_thread_init來在X WINDOW中初始化線程應用,另外在線程中要對GTK+控件進行操作時還要在操作前執行函數gdk_thread_enter來進入,操作完成後執行函數gdk_thread_leave來離開,在執行GTK+主循環時也是如此,GTK+以此來達到線程安全;下面代碼利用線程創建了一個在屏幕上沿順時針運動的圖像(24x24像素):
//thread.c #include <gtk/gtk.h> typedef struct _Ourarg Ourarg; struct _Ourarg { GtkWidget *fixed; GtkWidget *p_w_picpath; gint right; gint left; }; void p_w_picpath_go(Ourarg *arg) { gint x, y, toward; x = y = arg->left; toward = 1; for(;;) { g_usleep(1500); gdk_threads_enter(); gtk_fixed_move(GTK_FIXED(arg->fixed),arg->p_w_picpath, x, y); switch(toward) { case 1: x = x + 10; if( x > arg->right ) toward = 2; break; case 2: y = y + 10; if( y > arg->right ) toward = 3; break; case 3: x = x - 10; if( x < arg->left ) toward = 4; break; case 4: y = y -10; if( y < arg->left ) toward = 1; } gdk_threads_leave(); } } int main(int argc, char* argv[]) { GtkWidget *window; GtkWidget *vbox, *viewport, *button; GtkWidget *p_w_picpath, *fixed; Ourarg *arg; if(!g_thread_supported()) g_thread_init(NULL); gdk_threads_init(); gtk_init(&argc,&argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window),"線程測試"); g_signal_connect(G_OBJECT(window),"delete_event", G_CALLBACK(gtk_main_quit),NULL); gtk_container_set_border_width(GTK_CONTAINER(window),2); vbox = gtk_vbox_new(FALSE,0); gtk_container_add(GTK_CONTAINER(window),vbox); fixed = gtk_fixed_new(); gtk_widget_set_usize(fixed,340,340); viewport = gtk_viewport_new(NULL,NULL); gtk_box_pack_start(GTK_BOX(vbox),viewport,FALSE,FALSE,5); gtk_container_add(GTK_CONTAINER(viewport),fixed); p_w_picpath = gtk_p_w_picpath_new_from_file("ss.png"); gtk_fixed_put(GTK_FIXED(fixed),p_w_picpath,40,40); button = gtk_button_new_with_label("退出"); gtk_box_pack_start(GTK_BOX(vbox),button,FALSE,FALSE,5); g_signal_connect(G_OBJECT(button),"clicked", G_CALLBACK(gtk_main_quit),NULL); gtk_widget_show_all(window); arg = g_new(Ourarg,1); arg->fixed = fixed; arg->p_w_picpath = p_w_picpath; arg->left = 40; arg->right = 260; g_thread_create(p_w_picpath_go, arg, FALSE, NULL); gdk_threads_enter(); gtk_main(); gdk_threads_leave(); return FALSE; } |
基於線程安全考慮,你必須將下面代碼放在gtk_init函數之前執行:
if(!g_thread_supported()) g_thread_init(NULL); gdk_threads_init(); |
線程中執行一個死循環,不停的移動GtkFixed控件中的圖像控件,根據圖像的位置來改變方向。
(此圖像做得很不好,只是爲了證明程序的測試結果是可以正常運行的,完全可以刪除)