分类:
2006-05-25 14:40:13
我发现最好用示例来讨论代码。对于本文来说,我使用一个用 C 编写的名为 Hello World 的简短应用程序。虽然短小 —— 而且作为应用程序来说,基本没什么用 —— 但是这个应用程序的代码确实展示了在进行 GTK+ 编程时可能会碰到的一些最有趣的概念(参见清单 1)。
|
在进入细节之前,先看看运行 Hello World 程序时发生的情况:
gtk_main()
,激活主循环。
gtk_main_quit()
时才结束。以下几行初始化 GTK+ 和 i18n 支持:
|
C 程序员应当熟悉 main
声明。(如果不是 C 程序员,那么只需要知道这是应用程序开始执行的函数。)
下一行包含几个到 GtkWidget
类型的指针的声明。GTK+ 是一个面向对象的工具包。所以,它使用常见的面向对象概念(例如继承)来实现不同的部件。作为语言,C 缺乏对面向对象的内置支持。GTK+ 通过使用一些更聪明的技巧和 C 标准要求的一些有帮助的属性,克服了这个缺点。在这个方案中,对象由指针代表,而 GtkWidget
是 GTK+ 层次结构中的基本类型 —— 叫做 类,其他类都从它派生而来。所以,我把变量声明为 GtkWidget*
。
下三行是在程序开始的地方应当包含的调用,用来得到国际化接口的支持。注意,在真实的应用程序中,不会手动声明 LOCALEDIR
和 GETTEXT_PACKAGE
。编译系统将会处理这些声明。但是,在这个简单示例中,声明有助于澄清需要的内容。
最后一行调用 gtk_init()
。在进行其他 GTK+ 调用之前,必须调用这个函数,并把调用程序使用的参数传递给它。如果这个调用失败,就会从没有机会正确初始化自己的各个子系统得到多个错误消息。
这四行分别调用不同的 _new()
函数:
|
|
正如您所想,这些函数创建新的部件。所以,它们是代表部件的对象的构造函数。在 C++ 中,用特殊的方式标记构造函数,并用特殊语法进行调用。但是,因为 C 不支持面向对象,所以它们与普通的函数没有什么不同。只有添加到函数名尾部的 _new()
才表示这些函数实际上是构造函数。规定是:gtk_*
名称空间中的每个构造函数都返回一个到 GtkWidget
的指针。这样,通过声明这种类型的变量,可以直接把构造函数调用的结果分配给对应的变量。
如果查看单独的构造函数,可以看到它们接受不同的参数,对应着它们创建的部件的类型。具体来说,gtk_window_new (GTK_WINDOW_TOPLEVEL)
创建新的 TOPLEVEL 窗口;也就是与用户看成窗口的东西对应的部件,具有标题栏、关闭按钮或者窗口系统添加的其他元素。
对 label 和 button 的构造函数的调用做的工作正如您所料。但是,请注意传递给 button 的字符串周围的下划线和括号(_()
)。这个宏调用 gettext()
例程,并且对于界面转换至关重要。(关于 gettext
的更多信息,请参阅 参考资料。)
模样古怪的 gtk_vbox_new(FALSE, 0)
创建垂直框(VBox)。虽然这个部件与屏幕上的任何可视像素都不对应,但是它在 GTK+ 的控件布局中扮演着重要角色,您很快就会看到这一点。
这三行决定部件的布局:
|
这些行是对类型 GtkContainer
的面向对象方法的调用。如果查看应用程序编程接口(API)参考,可以看到 GtkContainer
继承自 GtkWidget
,而且它的全部方法都接受 GtkContainer*
作为第一个参数。所以,GtkContainer*
是方法要在上面操作的对象实例。因为变量是 GtkWidget*
类型的,而且 C 编译器不支持面向对象继承,所以需要让编译器相信:向需要 GtkContainer*
的函数传递这些变量是安全的。GTK_CONTAINER()
宏通过实现到类型安全版 GtkContainer
的类型转换做到了这一点。类型安全 意味着宏在进行类型转换之前,会验证指定操作可以在指定类型上安全地执行。如果宏不能执行指定操作,就会提供警告。
因为 GTK+ 使用方框布局 模型,所以不必显式地指定部件应当放在屏幕上的什么位置。相反,要指明部件放在其他哪个窗口部件内。在 Hello World 应用程序中,每个 gtk_container_add()
方法调用都告诉应用程序用第一个参数(或 双亲部件),并把第二个参数(或 子部件)放在双亲部件内。在这个示例中使用的 VBox
部件是一种布局部件,它垂直地排列子部件。这样,在它内部放置标签和按钮时,结果就是按钮显示在标签下。
这是需要做的全部工作。如果您曾经用过绝对定位(在某些工具包中使用的模型,例如 Win32)手动调整部件或重新设置部件大小,那么您会很高兴知道在 GTK+ 中所有这些都是自动完成的。
在创建并组织好部件之后,该给它们添加些逻辑了。GTK+ 像多数 GUI 工具包一样,是一个事件驱动框架。所以,它是围绕主循环 组织的。主循环在连续的检查-分配-睡眠周期上操作。当事件发生时,与这个事件对应的对象发出信号,通知主循环事件已经发生。然后主循环查询自己的信号和处理程序之间的内部映射表,也叫做 回调,并调用注册到指定对象的指定信号的处理程序。
在 Hello World 代码中,回调的注册看起来像这样:
|
请注意在 GTK+ 中,connect(连接) 到信号。第一行把 cb_delete
函数连接到 window
对象的 delete-event
信号。类似地,第二行把 cb_button_click
函数连接到 button
对象的 clicked
信号。请注意在第二个连接调用中的第四个参数 label
。稍后会看到它在 cb_button_click
函数中是如何使用的。
下面是 cb_delete
函数,它在用户关闭窗口时退出应用程序:
|
|
这个函数接受 GtkWidget*
和一个未指定的 data
指针(gpointer
是等价于 void*
的类型),因为 "delete-event"
的每个回调都必须符合这个原型。但是,这个函数不需要这些参数,所以它们被忽略了。它调用 gtk_main_quit()
例程以退出主循环。而且,该函数返回布尔值,因为为 GtkWidget
定义的 delete-event
信号的回调原型指明的是布尔返回值。布尔值决定 GTK+ 采取的行动。如果它返回 TRUE
值,那么事件就被认为已经得到处理,而且不调用默认处理程序(该默认处理程序从窗口系统删除部件)。有的时候,例如,如果想显示一条消息,请求尚未保存的数据,并根据用户的响应阻止窗口关闭,那么这个函数会很有用。
下面是 cb_button_click
函数,在用户点击按钮时,它修改显示的欢迎信息:
|
可以看到,这个函数与 cb_delete
函数类似,不同之处是它什么也不返回,而且它接受 GtkButton*
而不是 GtkWidget
。代码把 data
转换成到 GtkLabel
的指针。还记得在回调注册中的 label 参数么?现在每次调用回调时,data
指针都会包含到那个标签的指针。每当需要向回调传递额外的信息时,都可以使用 data
参数。类似地,如果需要访问发出信号的对象,就要使用第一个参数,在这个具体回调中是 button
。
在得到标签的指针后,代码使用 g_assert
宏来确定标签是否等于 NULL
。g_assert
宏是一个来自 Glib (GTK+ 使用的一个有用的 C 类型和例程库)的工具宏,如果传递给它的条件满足,就会中止程序 —— 在这个示例中,条件是 label
等于 NULL
。因为 label
等于 NULL
意味着程序员犯了错误,所以这可以确保在代码投入生产之前捕获到错误。
在回调连接好之后,gtk_widget_show_all()
函数使窗口 —— 即所有的部件 —— 都显示在屏幕上(参见图 1)。
当诸事就位,而且显示出来之后,gtk_main()
函数就激活主循环。主循环进入无限循环,等候事件并调用回调,直到有人关闭窗口,调用 gtk_main_quit()
为止。
注意:如果对代码仍有问题,请参阅附带的源代码。它与文章中介绍的代码完全相同,但是每一行都包含详细的注释。
要编译这个程序,需要 C 编译器和 GTK+ 的开发文件(头和库)。有关如何获取这些项目的信息,请参阅 参考资料。
在安装完文件之后,请解压源代码,进入源代码将要解压到的目录,并运行 make
:
|
注意:如果正在运行 Microsoft® Windows®,请不要运行 make
,而是打开 Microsoft Visual Studio™.NET 并运行 “hello” 项目。
|
可以在多个编程语言中使用 GTK+。要做到这点,需要使用绑定。绑定 是针对指定语言的特殊包,它用适合该语言的方式公开 GTK+ API。
例如,我已经把 Hello World 应用程序转换成 Python 和 C#。要在这些语言中运行 GTK+,除了 Python 和 Mono/.NET 之外,分别还需要 PyGTK 和 Gtk#(请参阅 参考资料)。
清单 6 显示了转换成 Python 的 Hello World 应用程序的代码。
|
由于紧凑的 Python 代码,应用程序的这个版本比它的 C 语言对应物更短。除此之外,看起来是相似的。注意,代码被转换成使用 Python 的习惯,但是 API 保持不变。
Gtk# 中的 Hello World 应用程序代码要比 C 版本略长,因为 C# 要求的声明很长。所以,我没有在这里包含完整的源代码。源代码包含在附加的下载中。下面快速查看一下 C 到 C# 的主要概念转换:
|
现在不再创建并设置新窗口,而是把 Gtk.Window
类放进一个子类,并把所有设置代码移动到构造函数。这种方法并非特定于 Gtk#。实际上,在 C 程序中,当需要窗口的多个拷贝时,经常使用这种方法。但是,在 C# 中使用子类如此之容易,所以即使对一个实例这么做也有意义 —— 特别是在考虑到 C# 要求至少声明一个类的时候,更是如此。
|
可以看到,GTK+ 信号被转换成地道的 C# 事件概念。名称也被稍加修改,以更好地符合 C# 的命名规范。
|
由于 C# 事件的构造方式,所以 delete-event
处理程序的原型略有不同。它不是从回调返回 true
,而是通过 args.RetVal
传递返回值。gtk_main()
和 gtk_main_quit()
分别被 Application.Run()
和 Application.Quit()
代替。
|
在使用 GTK+ 进行开发时,有几个工具可以让工作轻松些。其中最著名的几个工具是 Glade、Libglade 和 Devhelp。
Glade 是一个界面构建器 —— 这个程序可以图形化地构建应用程序,而不必手动从源代码开始构建。更重要的是第二个组件:Libglade。顾名思义,Libglade 支持读取可扩展标记语言(XML)格式,Glade 用 XML 保存用户界面描述。使用 Libglade,可以直接从这个描述构建应用程序的界面,而不需要任何代码。
图 2 显示了一个包含几个部件的简单的 Libglade 应用程序。
清单 8 显示了图 2 所示的 Libglade 应用程序的完整源代码。
|
可以看到,所有的事情只有 17 行代码,包括注释和空行。虽然真正的应用程序不会这么短,但是从代码的清晰性、模块性和可管理性来说,Libglade 带来了巨大的提高。
如果想进一步研究这个程序是如何构建的,可通过本文附带的下载中其余的示例进行了解。
Devhelp 是一个文档浏览器,是为了阅读用 gtk-doc 生成的格式的文档而设计的,gtk-doc 是构建 GTK+ 文档的标准工具,相关的项目,例如 Pango 和 GNOME 也使用它。使用 Devhelp,可以迅速地搜索函数索引并浏览已经安装的文档,从而可以更迅速地获得需要的信息。Devhelp 是一个 GNOME 应用程序。所以,要运行它,需要一个符合 POSIX 的操作系统,在上面运行 GNOME,例如 Linux® 或 Sun Solaris,还需要 GNOME 运行时库。不需要运行 GNOME 本身。
如果使用其他平台,也有许多其他方法可以阅读 GTK+ 文档。Mono 项目有 Monodoc 浏览器,它通常是与 Gtk# 参考一起预先装入的。GTK+ 的 Windows 安装器也通常包含适合各种开发工具的文档格式,例如 Visual Studio®。最后,总有使用 Web 浏览器在线阅读文档这个选项,但是推荐采用专用浏览器,因为它的速度快,还有针对在程序文档中进行搜索而设计的额外特性。
在这篇文章中,学习了 GTK+ 编程中使用的基本概念。还看到了如何在 C 语言之外的语言中使用 GTK+,在保持使用 GTK+ 的一般方式的同时,还使用了特定于这些语言的方式。最后,还介绍了有助于更快更好地开发应用程序的工具。
在本系列的最后一期中,将进一步观察 GTK+ 开发的另一个方面:部署。文章将详细分析各种选项,包括移植性的考虑和安装的简易。最后,将以更广阔的视野来看 GTK+ —— 作为一个有活跃社区的项目,它有助于构建出更好地为用户服务的应用程序。