1、QT和GTK+GTK+图形界面编程本章将介绍Linux下的图形界面编程,重点介绍基于C语言的具有面向对象特征的GTK+图形界面编程。主要介绍GTK+图形界面应用程序的框架、基本原理、常用控件的使用。本章重点:GTK+程序的基本结构。事件和消息处理。常用控件的使用。本章难点:理解GTK+应用程序的基本原理。熟悉常用控件的基本用法。12.1 Linux下的图形界面编程12.1.1 Qt和GTK+虽然Linux下的大多数开发是基于字符界面的,但在Linux环境下也可以开发出美观大方的图形界面。经过多年的发展,目前已经存在多种用于在Linux下开发图形界面程序的开发包,其中较为常用的是Qt和GTK+。
2、Qt是一个跨平台的图形用户界面开发库,它不仅支持Linux操作系统,还支持所有类型的UNIX以及Windows操作系统。Qt良好的封装机制使它模块化程度非常高,可重用性很强,Qt提供了丰富的API供开发人员使用。使用Qt开发的图形用户界面程序具有良好的稳定性和健壮性。桌面环境KDE(K Desktop EnVironment即K桌面环境)就是使用Qt作为其底层库开发出来的。由于Qt使用C+面向对象编程语言作为其开发语言,而许多在Linux下从事开发的程序员更喜欢或更习惯于用C语言。GTK+使用C语言作为开发语言。它基于LGPL授权,因此GTK+是开放源代码而且完全免费的。GTK+简单易用,执行
3、效率高。基于这些原因,GTK+拥有为数众多的拥护者。Linux的桌面环境GNOME就是建立在GTK+基础上。GTK+简介图12-1GTK+在几种相关的开发库中的位置。图12-1中每层除了与其上下相邻的两层有联系外,似乎与其他层没有关系。实际上,任何上层都可以调用位于它下面的各层提供的函数。例如,GTK+不仅可以调用GDK函数,也可以调用glib和C库函数。下面按层作简单的介绍,具体说明如表12-1所示。表12-1各层的具体含义层具 体 描 述C有两类C库函数可供调用,一类是标准C的库函数,如printf、scanf;另一类是Linux的系统调用,如open、read、write、forkgli
4、bglib是GDK、GTK+、GNOME应用程序常用的库。它包含内存分配、字符串操作、日期和时间、定时器等库函数,也包括链表、队列、树等数据结构相关的工具函数X它是控制图形显示的底层函数库,包括所有的窗口显示函数、响应鼠标和键盘操作的函数GDKGDK(GIMP绘图包)是为了简化程序员使用X函数库而开发的。X库是其低层函数库,GDK对其进行了包装,从而使程序员的开发效率大为提高GTK+GTK+就是GIMP工具包,它把GDK提供的函数组织成对象,使用C语言模拟出面向对象的特征,这使得用它开发出来的图形界面程序更为简单和高效。GTK+的一个重要组成部分是widget(控件,也称为小部件),按钮、文本
5、编辑框、标签等都是widgetGNOMEGNOME库是对GTK+的扩展,GNOME桌面环境用来控制整个桌面。GNOME使用GNOME对象和函数与桌面小部件交互,基本小部件由GTK+处理。GNOME为了方便程序员还增加了一些专门的小部件ApplicationApplication即应用程序,它完成窗口的初始化,创建并显示窗口,进入消息循环,等待用户使用鼠标或键盘进行操作简单地说,GTK+就是用C语言编写的用于开发图形界面程序的函数库。GTK+来源于GIMP(GNU Image Minipulation Program 即GNU图像处理程序)。GTK+在GDK(GIMP Drawing Kit 即
6、GIMP绘图包)基础上创建,对它进行封装。GTK+简单易用,它设计良好,灵活而富有扩展性。它是自由软件,这意味着它不仅开放源代码,而且还可以免费使用。由于它使用C语言作为其开发语言,而C语言是跨平台的,因此GTK+几乎可以在任何操作系统上使用。在安装Federo Core或者Red Hat Linux系列操作系统时,如果选择了安装应用程序开发包,那么操作系统安装完毕后,GTK+开发包就已经安装好了。如果没有安装,请从网络上(http:/www.gtk.org)免费下载一份GTK源代码并安装到系统上,也可以插入Linux安装光盘在系统提示下进行安装。由于安装过程非常简单,这里就不再赘述了。12.
7、2 一个简单的例子先来看一个简单的GTK+图形界面程序的例子,了解这类程序的一般框架。这个程序创建了一个窗口,并在窗口中放置了一个按钮,实现代码如例12-1所示。例12-1 gtkwin.c#include/*定义回调函数hello,单击按钮时系统自动调用*/void hello(GtkWidget *widget,gpointer *data)g_print(button clicked and data = %sn,(char *)data);/*定义回调函数destroy,关闭窗口时系统自动调用*/void destroy(GtkWidget *widget,gpointer *data
8、) gtk_main_quit();int main(int argc,char *argv)/*定义指向控件的指针*/GtkWidget *window;GtkWidget *button;/*初始化图形显示环境*/gtk_init(&argc,&argv);/*创建窗口,并设置当关闭窗口时,要执行的回调函数*/window = gtk_window_new(GTK_WINDOW_TOPLEVEL);g_signal_connect(GTK_OBJECT(window),destroy,GTK_SIGNAL_FUNC(destroy),NULL);/*设置窗口的属性*/gtk_contain
9、er_border_width(GTK_CONTAINER(window),20);/*创建按钮,并设置当单击按钮时,要执行的回调函数*/button = gtk_button_new_with_label(Hello World);g_signal_connect(GTK_OBJECT(button),clicked,GTK_SIGNAL_FUNC(hello),I am from button);/*将按钮加入到窗口中*/gtk_container_add(GTK_CONTAINER(window),button);/*显示按钮和窗口*/gtk_widget_show(button);gt
10、k_widget_show(window);/*进入消息处理循环*/gtk_main();return 0;编译并运行:rootmci tmp# gcc -o gtkwin gtkwin.c pkg-config -libs -cflags gtk+-2.0rootmci tmp# ./gtkwin注意:编译命令中的字符串pkg-config -libs -cflags gtk+-2.0两边是反引号(在键盘上位于数字字符1的左边)。运行程序,显示如图12-2所示的界面:图12-2 运行界面程序说明。(1)所有的GTK+程序中都必须包含头文件gtk/gtk.h,它声明了所有GTK+编程中要使用的
11、常量、数据结构和函数。(2)所有GTK+程序开始都要调用函数gtk_init(gint *argc,gchar * argv)。该函数定位和打开图形显示,并对颜色、信号等进行初始化。在命令行输入的参数由该函数传递给GTK+,该函数读取并获得与它有关的命令行参数。(3)函数gtk_window_new(GTK_WINDOW_TOPLEVEL)用于创建顶级窗口,GTK+程序的主窗口被称为顶级窗口。虽然一个程序可以创建多个顶级窗口,但通常只创建一个。(4)图形界面下,用户的任何一个操作(如单击鼠标左键,按下键盘上的某个键)都称为发生了一个事件,GTK+都有相应的消息信号产生,如果程序中定义了处理该消
12、息信号的函数,在事件发生后,消息信号处理函数会自动调用。这样的消息信号处理函数也称为回调函数,因为这种函数虽然是在程序里定义,但程序中并没有显式调用而是由系统在事件发生后自动调用。hello()和destroy()就是两个处理消息的回调函数。destroy函数中调用了GTK+函数gtk_main_quit,它使程序退出gtk_main()并完成一些清理工作。g_signal_connect函数用于在控件和消息处理函数间建立关联,该函数的第一个参数为产生消息的控件,第二个参数是消息名,第三个参数是消息发生后要调用的函数名,第四个是传递给消息处理函数的参数,可以为空值(即NULL)。关于消息和回调
13、函数的详细内容请参考下一节。(5)gtk_container_border_width函数用于设置窗口边框的宽度,这是一个设置窗口属性的函数。(6)gtk_button_new_with_label函数创建一个带文本标签的按钮,它完成内存分配,并把所分配到的内存的首地址赋给GtkWidget类型的指针。(7)函数gtk_container_add通知GTK+将按钮加入到主窗口中,函数gtk_widget_show用于显示控件。(8)gtk_main()使GTK+进入消息处理循环。每个GTK+应用程序都有一个gtk_main函数,该函数使程序进入休眠状态。当有事件发生,如果程序中有相应的处理函数
14、,gtk_main()就调用相应的消息处理函数。(9)为了方便编译,可以在源程序所在目录下编写一个Makefile文件:CC=gccprogram=gtkwin#PATH += /usr/include/gtk2.0LDLIBS=pkg-config -libs gtk+-2.0CFLAGS= -Wall -g pkg-config -cflags gtk+-2.0$(program):$(program).o$(CC) $(LDLIBS) $(program).o -o $(program)$(program).o:$(program).c$(CC) $(CFLAGS) -c $(progr
15、am).cclean:-rm -f $(program)-rm -f *.o此时编译程序可以简化为:rootmci tmp# make要删除编译所产生的中间文件和可执行文件,可以执行命令:rootmci tmp# make clean如果要编译其他GTK+程序,把Makefile文件中的gtkwin改为相应源程序的文件名即可。Makefile的编写和make命令的使用请参考第5章。例12-1程序中用到了一些GTK+预定义的函数和数据类型,表12-2和表12-3对其作一个简单的介绍。表12-2GTK+预定义的函数前 缀含 义Gglib定义的数据结构gglib声明的数据类型g_glib定义的函数g
16、tk_GTK+定义的函数GtkGTK+库的对象或数据结构GTKGTK+定义的宏或者常量表12-3GTK+预定义的数据类型GTK+的数据类型C语言数据类型gcharchargintintglonglonggbooleanchargfloatfloatgdoubledoublegucharunsigned charguintunsigned intgulongunsigned longgpointervoid *gint8在任何平台上都是8位的整型gint16在任何平台上都是16位的整型gint32在任何平台上都是32位的整型guint8在任何平台上都是8位的无符号整型guint16在任何平台上都
17、是16位的无符号整型guint32在任何平台上都是32位的无符号整型12.3 消息和回调函数图形用户界面的程序是事件驱动的程序。程序进入gtk_main函数后,等待事件的发生,一旦发生某个事件,相应的信号将产生。如果程序中定义了相应的消息处理函数,系统会自动进行调用。消息处理函数(或称回调函数)的原型是:void callback_func(GtkWidget *widget,gpointer func_data);参数widget指向要接收消息的控件,参数func_data指向消息产生时传递给该函数的数据。函数g_signal_connect在控件和消息处理函数间建立关联,该函数的原型是:g
18、ulong g_signal_connect(GtkObject *object,gchar *nameGcallback callback_func,gpointer func_data);各参数说明如下。object:指向产生消息的控件。name:消息或事件的名称。callback_func:事件发生后要执行的回调函数。func_data:传递给回调函数的数据,与callback_func()的第二个参数相同。该函数的返回值用于区分一个控件的一个事件对应的多个处理函数。一个控件上可以发生多个事件,比如单击一个按钮,双击一个按钮。对于一个控件上的每个事件可以有0个、1个或多个处理函数。该事件
19、发生时,将按声明的顺序逐个调用这些函数。对应于某个事件,如果控件没有定义处理函数,那么事件发生时将没有响应,系统忽略此事件。还有一个与g_signal_connect()类似的函数:gint g_signal_connect_swapped(GtkObject *object,gchar *nameGCallback callback_func,GtkObject *slot_object);它的第四个参数指向一个GTK+控件的指针。它与g_signal_connect()的区别在于相应的回调函数只有一个参数:void callback_func(GtkObject *object);通常ob
20、ject指向一个控件。可以在上一节的例子中gtk_container_add(GTK_CONTAINER(window),button);之前加上一条语句:g_signal_connect(GTK_OBJECT(button),clicked,GTK_SIGNAL_FUNC(gtk_widget_destroy),GTK_OBJECT(window);则对应于button按钮的clicked事件有两个处理函数,一个是程序中定义的回调函数hello,还有一个是GTK+预定义的gtk_widget_destroy()。gtk_widget_destroy()的作用与程序中的destroy()相同。
21、编译运行程序,如果单击按钮,系统先调用hello函数(因为它先与button控件建立关联)在命令行上打印出一行消息,然后调用gtk_widget_destroy()退出程序。如果要删除控件和消息处理函数的关联,可以调用g_signal_disconnect(),该函数的原型是:void g_signal_handler _disconnect(GtkObject *object ,gulong id);参数说明如下。object:要删除消息处理函数的控件。id:g_signal_connect()或g_signal_connect_swapped()函数的返回值。下面这个函数可以删除某控件的所
22、有消息处理函数:void g_signal_handlers_destroy(GtkObject *object);12.4 GTK+的面向对象机制对于那些没有接触过面向对象语言的读者来说,本节的内容可能比较难以理解。不过没有关系,这并不会影响后面内容的掌握。介绍本节的内容只是为了简单地介绍一下GTK+中是如何模拟面向对象机制的。面向对象编程语言(如C+、Java)把数据和对数据的操作封装在一起构成类,由类来产生对象,由对象来构建程序。类中对数据的操作由函数来完成,这种函数被称为成员函数或方法。面向对象语言通过继承、重载、多态等机制大大增强软件的可重用性和可维护性。C语言虽然不是面向对象语言,
23、但GTK+以及建立在其上的GNOME库却使C语言模拟出了一些典型的面向对象机制,如封装、继承和多态。为了较好的理解GTK+程序,了解GTK+中的面向对象机制也是很有必要的。对象的一个主要特性是将数据和对数据的操作封装在一起,受保护的私有数据只能通过成员函数才能访问和修改。GTK+使用C语言的结构体来模拟对象,虽然有些缺陷但基本模拟出了对象的基本特征。有了对象作为基础,通过在对象中加入新的数据和对这些数据进行操作的函数,就实现了继承。被继承的类(类相当于一种自定义数据类型,由类来定义对象)称为父类或基类,由基础类派生出来的类称为子类或派生类。子类继承了父类的数据和对这些数据进行操作的成员函数,并
24、加入了新的数据和成员函数,实现了对原有父类的重用和扩展,从而实现了可重用性和可扩展性。GTK+中有一个类,它是所有其他类的父类,这个类是GtkObject。GTK+中最常用的按钮控件也是一个类,它继承自GtkObject。它与GtkObject的继承关系是:GtkObject GtkWidget GtkContainer GtkBin GtkButton使用C语言如何模拟继承呢?对象(类)是由结构体模拟的,每一个子类所在的结构体都包含了父类的结构体,子类结构体的第一个成员是其父类结构体,示例代码如下:struct GtkObject ;struct GtkWidget GtkObject ob
25、ject;struct GtkContainer GtkWidget widget;struct GtkBin GtkContainer container;struct GtkButton GtkBin bin;从上述代码可以看到,每个子类都包含了其父类的所有数据,并且父类的数据位于子类结构体的开始。对于一个GtkButton 类型的button控件变量(它其实是一个指向GtkButton结构体的指针),通过宏GTKBIN(button)就得到了其父类(GTK+预定义的宏GTKBIN其实是进行了强制类型转换,把一个GtkButton类型的指针强制转化为GtkBin类型的指针)。例12-1中的
26、GTK_OBJECT(button)就是进行了这样的转换。为了便于理解,我们写一个测试程序,如例12-2所示。例12-2 test.c#include#include#define FATHER(child) (struct Father *)(child)void print1(int i)printf(this is father and i = %dn,i);void print2(int i)printf(this is child and i = %dn,i);struct Father int a;void (*pointer1_to_function)(int);struct C
27、hild struct Father f;int b;void (*pointer2_to_function)(int);void father_member_funtion(struct Father *f,char *string)printf(n);f-pointer1_to_function(f-a);printf(%snn,string);int main()struct Child *p_child;p_child = (struct Child *)malloc(sizeof(struct Child);p_child-f.a = 10;p_child-f.pointer1_to
28、_function = print1;p_child-b = 20;p_child-pointer2_to_function = print2;p_child-pointer2_to_function(p_child-b);struct Father *p_father = FATHER(p_child);p_father-pointer1_to_function(p_father-a);father_member_funtion(p_father,hello);return 0;程序输出:this is child and i = 20this is father and i = 10thi
29、s is father and i = 10hello程序说明。(1)结构体Father相当于GTK+中的父类,而结构体Child就相当于子类。结构体Father有一个成员变量和一个成员函数(实际上是一个指向函数的指针,函数指针的内容请参考第4章4.3.4指针和函数一节)。结构体Child在其头部包含了结构体Father,并增加了一个成员变量和一个成员函数(也是一个函数指针)。结构体Father和Child模拟了类,Child模拟继承了Father。(2)程序定义了一个指向结构体Child的指针,并对Child中的成员进行了初始化。然后调用了结构体Child的成员函数pointer2_to_f
30、unction。通过宏FATHER(p_child)将指针p_child强制转换为指向Father结构体的指针。事实上,p_child和p_father的值是一样的,它们都保存着结构体Child的首地址。宏FATHER(p_child)类似于例12-1程序gtk_container_add(GTK_CONTAINER (window),button)中的GTK_CONTAINER(window)。我们注意到,p_father调用了它自己的成员函数pointer1_to_function。(3)father_member_funtion函数是类Father成员函数的另一种实现方法,这种方法避免了在结构体Father中保存函数指针。GTK+定义了很多生成对象或对对象进行操作的函数。例如,下面就创建了一个对象:GtkWidget *button;button = gtk_button_new_with_label(label);所有创建对象的函数在其名称上都有new这个词。函数gtk_button_new_with_label创建了一个显示文本的
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1