HelloWorld

用GTK+寫的HelloWorld

下面的代碼是筆者用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+的世界。


國際化編程

gettext軟件包

上面的程序運行是主窗口顯示爲英文,我們完全可以將其改爲中文,這樣單一的語言版本不適於應用的國際化,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與打包

自動生成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控件中的圖像控件,根據圖像的位置來改變方向。


(此圖像做得很不好,只是爲了證明程序的測試結果是可以正常運行的,完全可以刪除)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章