- 論壇徽章:
- 1
|
GTK簡介
應(yīng)用GTK+編程
作者:宋國偉 本文選自:IBM DW中國 2003年04月18日
GTK+采用具有OO特色的C語言開發(fā)框架,這使它在開發(fā)GUI應(yīng)用程序能和操作系統(tǒng)緊密結(jié)合,同時具有很大簡潔性,其中的很多代碼只要簡單的復(fù)制和更改即可完成,只用一個C源代碼文件就可以創(chuàng)建一個LINUX下的GUI程序。
用GTK+寫的HelloWorld:
下面的代碼是筆者用GTK+編寫的一個HelloWorld例程,編譯后運行顯示一個帶按鈕的窗口,點擊按鈕會彈出提示信息對話框。
//hello.c
#include <gtk/gtk.h>;
//主窗口中按鈕的回調(diào)函數(shù)
void on_button_clicked(GtkWidget* button, gpointer userdata)
{
GtkWidget *dialog;
//創(chuàng)建帶確認(rèn)按鈕的對話框,父控件為空
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);//銷毀對話框
}
//主函數(shù)
int main(int argc, char* argv[])
{
GtkWidget *window, *button;
//初始化GTK+程序
gtk_init(&argc, &argv);
//創(chuàng)建窗口,并為窗口的關(guān)閉信號加回調(diào)函數(shù)以便退出
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);
//創(chuàng)建按鈕控件,為單擊信號加回調(diào)函數(shù),將其放入窗口中
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);
//下面函數(shù)顯示窗口控件同時顯示其中的所有其它控件
gtk_widget_show_all(window);
gtk_main();
return FALSE;
}
可以直接用命令"gcc `pkg-config -cflags -libs gtk+2.0` hello.c -o hello"來編譯上面的代碼,但最好做一個Makefile文件內(nèi)容如下:
CC = gcc
all:
$(CC) `pkg-config --cflags --libs gtk+-2.0` hello.c -o hello
這樣的話可以用make命令來編譯,使得簡單了許多,也不容易出錯了。再次強調(diào)的是關(guān)于引號的問題,很多初學(xué)者常犯這個錯誤,[`]是[~]下面的那個單引號,而非['];這涉及到了LINUX SHELL編程中的命令引用,LINUX下的標(biāo)準(zhǔn)的BASH是支持命令引用的,而其它的SHELL就不一定了。
下兩圖分別為程序的運行時的窗口和點擊Hello World按鈕彈出的對話框:
初始化、主循環(huán)與退出
與MS WINDOWS下用C開發(fā)GUI程序不同,GTK+不用WinMain函數(shù),由C語言中的標(biāo)準(zhǔn)格式的main函數(shù)直接切入,這在UNIX操作系統(tǒng)家族中是統(tǒng)一的。函數(shù)gtk_init標(biāo)致GTK+程序的開始,它的兩個參數(shù)是main函數(shù)的兩個參數(shù)的地址。在此函數(shù)之后就可以處理程序的各種相關(guān)部分,如控件的創(chuàng)建、顯示、為控件的信號加回調(diào)函數(shù)、設(shè)定或修改控件的屬性等。最后執(zhí)行g(shù)tk_main函數(shù),程序進入主事件循環(huán),開始接收信號并為信號調(diào)用其相應(yīng)用的回調(diào)函數(shù)。函數(shù)gtk_main_quit用來結(jié)束主事件循環(huán),即退出GTK+程序的運行。
控件的創(chuàng)建、顯示與布局
GTK+中的控件分為容器控件和非容器控件1,非容器控件主要是基礎(chǔ)的GUI元素,如文字標(biāo)簽、圖像、文字錄入控件等,容器控件有多種,共同點是可以按一定方式來排放其它控件,GTK+以此形成了獨特的GUI界面布局風(fēng)格。GTK+控件的創(chuàng)建函數(shù)一般形式為:gtk_控件名_new(參數(shù)…)或gtk_控件名_new_with_參數(shù)名(參數(shù)…),它的返回值為GtkWidget型的指針,創(chuàng)建完成后就可以調(diào)用gtk_widget_show函數(shù)來顯示或隱藏此控件,或用相關(guān)的函數(shù)來修改控件的屬性。
信號連接與回調(diào)函數(shù)
GTK+用信號和回調(diào)函數(shù)的方式來處理來自外部的事件,控件間繼承有其父控件的相同信號,不同的控件也有各自不同的信號,如按鈕控件有"clicked"信號,而文字標(biāo)簽控件則沒有此信號。GTK+2.0采用宏g_signal_connect來完成信號與回調(diào)函數(shù)的連接,這是它與GTK+1.X版的一個關(guān)鍵不同之處,這個宏有四個參數(shù),第一個參數(shù)是連接信號的對象,如此例中的button或window,要用G_OBJECT宏來轉(zhuǎn)換一下,即將對象的類型GtkWidget轉(zhuǎn)換為GObject類型,格式一般為G_OBJECT(button);第二個參數(shù)為字符串格式的信號名;第三個參數(shù)為回調(diào)函數(shù)名,用G_CALLBACK宏來轉(zhuǎn)換一下;第四個參數(shù)為要傳給回調(diào)函數(shù)的參數(shù)的指針。如上例中為按鈕的"clicked"的信號加的回調(diào)函數(shù)"on_button_clicked":
g_signal_connect(G_OBJECT(button),"clicked",
G_CALLBACK(on_button_clicked),(gpointer)"你好!\n自由的世界。" ;
細心的讀者馬上會看到此宏的一個缺點,即只能為回調(diào)函數(shù)傳遞一個參數(shù),當(dāng)然聰明的讀者馬上也會想到采用結(jié)構(gòu)類型來傳遞多個參數(shù)。上面的內(nèi)容對初學(xué)者似乎復(fù)雜了些,只要過了這個門檻,事實上就步入了GTK+的世界。
國際化編程
gettext軟件包
上面的程序運行是主窗口顯示為英文,我們完全可以將其改為中文,這樣單一的語言版本不適于應(yīng)用的國際化,GTK+中用gettext軟件包來實現(xiàn)國際化,使這一問題變得非常簡單。gettext軟件包是GNU工程中解決國際化問題的重要工具,目前版本是0.11.x,支持C/C++和JAVA語言,它在開源界應(yīng)用相當(dāng)廣泛,GNOME/GTK+的國際化問題都是用它來解決的,正常的情況下GNU/LINUX系統(tǒng)是默認(rèn)安裝這一軟件包的。
代碼實現(xiàn)
首先是在源代碼中加入相關(guān)的C語言頭文件如下:
#include <libintl.h>; //gettext支持
#include <locale.h>; //locale支持
然后是定義宏,下面的定義形式在GNOME/GTK+中應(yīng)用的標(biāo)準(zhǔn)格式:
#define PACKAGE "hello" //軟件包名
#define LOCALEDIR "./locale" //locale所在目錄
#define _(string) gettext(string)
#define N_(string) string
在程序的主函數(shù)中加入下面相關(guān)函數(shù):
bindtextdomain(PACKAGE,LOCALEDIR);
//以上函數(shù)用來設(shè)定國際化翻譯包所在位置
textdomain(PACKAGE);
//以上函數(shù)用來設(shè)定國際化翻譯包名稱,省略了.mo
相關(guān)的字符串修改
將代碼中需要國際化--即多語言輸出的字符串改寫為_()宏,代碼如下:
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!" ));
... ...
生成相關(guān)文件與翻譯
完成以上修改后,執(zhí)行如下命令:xgettext -k_ -o hello.po hello.c,它的功能是將hello.c中的以下劃線開始括號中(如宏定義所示)的字符串加入到hello.po文件中。po文件的頭部可以加入軟件包的名稱、版本、翻譯者的郵件地址等,po文件中以#開始的行為注釋內(nèi)容,以下為省略了頭部的hello.po文件內(nèi)容,msgid后面的內(nèi)容為英文,msgstr后面的內(nèi)容為翻譯的中文,翻譯好后保存為UTF8格式。
#: hello.c:26 hello.c:29
msgid "Hello World!"
msgstr "你好世界!"
#: hello.c:31
msgid "Hello, the Free World!"
msgstr "你好,自由的世界!"
下一步執(zhí)行命令:msgfmt -o hello.mo hello.po將 hello.po文件格式化為hello.mo文件,這樣程序在運行時就能根據(jù)當(dāng)前l(fā)ocale的設(shè)定來正確讀取mo文件中的數(shù)據(jù),從而顯示關(guān)語言的信息了。關(guān)于.mo文件的位置,本程序設(shè)在./locale目錄下的中文目錄zh_CN下的LC_MESSAGES目錄下,即./locale/zh_CN/LC_MESSAGES 目錄下,在REDHAT中默認(rèn)的目錄是/usr/share/locale。將此步驟生成的mo文件復(fù)制到相應(yīng)的目錄下,將locale設(shè)為簡體中文,再運行此程序,測試結(jié)果就變?yōu)橹形牧耍ㄈ缦聢D),如locale設(shè)為英文則顯示仍為上面的英文信息。
自動生成Makefile與打包
自動生成Makefile文件是很多LINUX編程愛好者的愿望,事實上只要你運用好AUTOCONF和AUTOMAKE這兩個工具,就會很容易的生成Makefile文件,并能實現(xiàn)打包(生成*.tar.gz格式的源代碼包)功能。與這兩個工具相關(guān)的配置文件分別是configure.in和Makefile.am。只要我們弄懂這兩個文件的格式和相關(guān)的宏,就完全可以了。
操作過程
前提是做好相應(yīng)的目錄和源程序文件,如本例中在hello目錄中的hello.c等。
1、首先執(zhí)行autoscan命令,生成configure.scan文件;
2、執(zhí)行mv configure.scan configure.in,將其改名;
3、編輯configure.in,在AC_INIT(hello.c)之后加入一行,AM_INIT_AUTOMAKE(hello,1.0)表示軟件包名為hello,版本為1.0,如此在make編譯后,執(zhí)行make dist會生成一個名為 hello-1.0.tar.gz源代碼包,這樣是比較附合GNU開源標(biāo)準(zhǔn)的格式。在最后一行AC_OUTPUT()的括號中加入Makefile,表示輸出Makefile文件(configure.in文件中還有許多宏定義,詳細用法可以參考autobook一書),至此編輯configure.in文件結(jié)束;
4、執(zhí)行aclocal命令,生成aclocal.m4宏文件;
5、執(zhí)行autoconf命令,生成configure shell可執(zhí)行腳本;
6、編輯Makefile.am文件,內(nèi)容如下:
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命令的參數(shù),表示為外部的,不按GNU標(biāo)準(zhǔn)(即不加說明、安裝、更改記錄等文件) ;第二行表示包含文件的目錄;第三行表示動態(tài)鏈接庫的目錄和所要鏈接的庫;第四行表示輸出的可執(zhí)行文件名;最后一行表示可執(zhí)行文件的源程序文件,可以有多個文件名。
7、執(zhí)行命令automake --add-missing -copy,表示創(chuàng)建Makefile.in文件并加入遺失的文件,同時復(fù)制過來(默認(rèn)情況下是做符號鏈接,這在不同的文件系統(tǒng)間會出問題),至此操作完成。
編譯、測試、安裝與打包
執(zhí)行./configure 生成Makefile;執(zhí)行make編譯;執(zhí)行./hello運行此程序;執(zhí)行make install來安裝,默認(rèn)情況下是將可執(zhí)行文件hello復(fù)制到/usr/local/bin目錄下;執(zhí)行make dist會在當(dāng)前目錄下生成hello-1.0.tar.gz源代碼包,如此就可以將你的源代碼包向外界發(fā)布了。
使用線程
在GTK+中應(yīng)用線程, 除了GLIB中的g_thread_init和g_thread_supported兩個函數(shù)外,還要用到gdk_thread_init來在X WINDOW中初始化線程應(yīng)用,另外在線程中要對GTK+控件進行操作時還要在操作前執(zhí)行函數(shù)gdk_thread_enter來進入,操作完成后執(zhí)行函數(shù)gdk_thread_leave來離開,在執(zhí)行GTK+主循環(huán)時也是如此,GTK+以此來達到線程安全;下面代碼利用線程創(chuàng)建了一個在屏幕上沿順時針運動的圖像(24x24像素):
//thread.c
#include <gtk/gtk.h>;
typedef struct _Ourarg Ourarg;
struct _Ourarg {
GtkWidget *fixed;
GtkWidget *image;
gint right;
gint left;
};
void image_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->;image, 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 *image, *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);
image = gtk_image_new_from_file("ss.png" ;
gtk_fixed_put(GTK_FIXED(fixed),image,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->;image = image;
arg->;left = 40;
arg->;right = 260;
g_thread_create(image_go, arg, FALSE, NULL);
gdk_threads_enter();
gtk_main();
gdk_threads_leave();
return FALSE;
}
基于線程安全考慮,你必須將下面代碼放在gtk_init函數(shù)之前執(zhí)行:
if(!g_thread_supported()) g_thread_init(NULL);
gdk_threads_init();
線程中執(zhí)行一個死循環(huán),不停的移動GtkFixed控件中的圖像控件,根據(jù)圖像的位置來改變方向。
。ù藞D像做得很不好,只是為了證明程序的測試結(jié)果是可以正常運行的,完全可以刪除)
結(jié)束語
學(xué)習(xí)GTK+的最好的方法是研究GTK+源碼包中帶的例程和研究其它應(yīng)用GTK+的開源軟件的源碼包,在國內(nèi)最大LINUX論壇上有很多這方面的高手。
如果你的英文足夠好的話還可以訂閱GTK+應(yīng)用開發(fā)的郵件列表,這樣你每天都可以收到幾封關(guān)于GTK+開發(fā)的郵件,筆者最多時一天收到過近30封郵件,其中有很多人會熱心的回答你的問題或幫你出主意。
在QT和GTK+的選擇上,經(jīng)常能看到一些網(wǎng)友的爭論。對于目前國內(nèi)LINUX平臺上的GUI應(yīng)用程序的開發(fā),擱置爭議、潛心學(xué)習(xí)、交流心得、形成合力、做出具有應(yīng)用前景和自己特色的開源項目才是自由社區(qū)的當(dāng)務(wù)之急。
注1:可以參考IBM developerWorks網(wǎng)站上的文章《GTK+2.0中的容器控件和布局技巧》
關(guān)于作者
宋國偉,鄉(xiāng)村小學(xué)英語教師,他是《GTK+2.0編程范例》(清華大學(xué)出版社出版)一書的作者,業(yè)余時間致力于用GTK+開發(fā)LINUX GUI應(yīng)用程序,可以通過電子郵件地址gwsong_52@sohu.com與他聯(lián)系。
『引自 IBM DW中國』 |
|