全部博文(1159)
分类: C/C++
2011-11-17 16:34:50
原文链接:
Micah Carrick
Part 3
Writing a Basic Program to Implement the Glade File
在这一部分, 我将示范一个非常简单的程序, 用来解析我们在 part1 中用 Glade3 创建的 GUI 文件 tutorial.glade , 并显示我们 GTK+ 文本编辑器的主窗口。我会先讨论 GTK+ 的一些概念然后分别以 C 语言和 Python 语言来给出实现代码。如果你选择其中之一, 那么你可以跳过另外一门语言的介绍部分。
Setting Up Your Development Environment
为完成这部分向导的 GTK+ 编程,你需要一个文本编辑器, 一个终端,, GTK+ 开发库, 以及 Devhelp 。 Devhelp 是开发人员的参考帮助。如果你是 Linux 新手,那么有更多的选择等着你。并没有一个所谓标准的编辑器或者是 IDE , 许多开发人员都仅仅使用自己喜欢的文本编辑器和终端。尽管也有一些功能齐全的 IDE 可以选择, 不过你应该首先使用比较简单的文本编辑器加上终端即可, 以免被 IDE 的特性和自动化功能弄迷糊, 浪费不必要的时间。
我使用 gedit , GNOME 的默认文本编辑器。 gedit 有一些有用的插件可以使用。我写了一个
可以让你快速地跳转到源代码的函数定义。
你需要的开发库取决于你使用 C 还是使用 Python , 对于不同的开发平台及发布平台会有很大不同, 不过我会对此提供一些有用的信息。如果在安装开发包和发布产品时还出现问题的话, 可以到 寻找答案。
在 Linux 上, 你通常可以使用分发包管理器获得你所需要的所有开发包并解决其依赖性。例如, 在 Ubuntu 上你只需要使用命令: sudo aptitude install libgtk-2.0-dev , 即可安装好 GTK+ 开发包,以及其相关依赖项。
注意,需要安装的是 "development package" , Ubuntu/Debian 中以 -dev 结尾的包, 在 Redhat/Fedora 中以 -devel 结尾。这种包包括了使用相应的库来开发应用程序所需要的头文件和其它包含文件。请记住, "package" 允许你能过运行应用程序,而 "package-dev" 或 "package-devel" 允许你使用库开发应用程序。
另外你还会见到 -doc 后缀的包, 比如 "libgtk2.0-doc" ,这是相应库的开发文档, 安装之后你就可以使用 Devhelp--the GNOME developer's help browser . 来浏览相关文档。
如果你使用 C ,你应该安装以下开发包及其依赖项 :build-essential, libgtk2.0-dev, libgtk2.0-doc, libglib2.0-doc, devhelp.
如果你用 Python , 你应该安装以下开发包及其依赖项: python2.5-dev, python2.5-doc, python2.5-gtk2, python-gtk2-doc, python-gobject-doc, devhelp.
GtkBuilder and LibGlade
如果你能回忆起来的话, 我们在 part1 利用 Glade 创建的 tutorial.glade 文件是一个描述 GUI 界面的 XML 文件。实际的 GUI 是由我们的应用程序来创建的。因此, 应用程序需要解析 XML 文件并创建 widgets 的对象实例。不过这个任务可以使用两个库来完成:
Libglade 库, 用来解析 glade 文件并创建 widgets 对象实例。使用 Libglade 库是最常用的方式, 在其它一些开发向导或教程中都能看到。然而, 自此 GTK+2.12 , 就包含了一个叫 GtkBuilder 的对象, 它是 GTK+ 自身的一部分并用来取代 Libglade 库。也因此,在我们的开发向导中我们将使用 GtkBuilder 。不过, 你得知道的是, 你在网络上看到的教程中凡是使用 Libglade 库的, 都可以使用 GtkBuilder 来代替。
在写本系列的时候, GtkBuilder 还是一个新东西, 因此 Glade 尚未支持 GtkBuilder 格式文档,(译者注:自 Glade3.6.0 版本, 提供了对两种格式的支持即 Libglade 和 GtkBuilder 格式) GtkBuilder 格式就是 XML 格式文件, 但是有一点不同。这意味着 GtkBuilder 使用 glade 文件时, 需要进行格式转换。 GTK2.12 提供了转换脚本命令来做这件事, 这个脚本连同开发库一起安装。现在你已经装上了 ^_^
你可以参考关于 Libglade/GtkBuilder 常见问题的答案: .
使用如下命令来把 Libglade 格式文件 tutorial.glade 转换为 GtkBuilder 格式文件 tutorial.xml 文件: gtk-builder-convert tutorial.glade tutorial.xml
而 tutorial.xml 文件是我们的应用程序将要用来进行解析的, 不过我们仍然需要 glade 文件以便可以使用 Glade 进行修改。这是必需的,直到 Glade3.6 版本支持 GtkBuilder 格式文件为止。(译者注: Mar 16 2009 , Glade3.6.0 released 。此版本及以后版本可以直接使用 xml 文件并且自动保存所做的修改)
The Minimal Application
现在我们开始写代码!首先回顾一下到目前为止我们都做了哪些工作
1 使用 Glade ,创建了描述用户界面的 tutorial.glade 文件
2 我们选择了开发语言 C 和 Python
3 准备了一个文本编辑器和一个终端
4 安装了进行 GTK+ 开发所需要的所有库
5 使用 gtk-builder-convert 命令把 tutorial.glade 文件转换成了 GtkBuilder 使用的 tutorial.xml 文件。
现在, 在深入讲解每一行代码的细节之前, 先来写一个最简单的程序来验证一切都能正常工作, 并熟悉开发流程。因此, 打开你的文本编辑器, 输入以下内容:
C 语言
#include
void
on_window_destroy (GtkObject *object, gpointer user_data)
{
gtk_main_quit();
}
int
main (int argc, char *argv[])
{
GtkBuilder *builder;
GtkWidget *window;
gtk_init (&argc, &argv);
builder = gtk_builder_new ();
gtk_builder_add_from_file (builder, "tutorial.xml", NULL);
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
gtk_builder_connect_signals (builder, NULL);
g_object_unref (G_OBJECT (builder));
gtk_widget_show (window);
gtk_main ();
return 0;
}
命名为
main.c
并保存到
tutorial.xml
所在目录。
Python 语言
import sys
import gtk
class TutorialTextEditor:
def on_window_destroy(self, widget, data=None):
gtk.main_quit()
def __init__(self):
builder = gtk.Builder()
builder.add_from_file("tutorial.xml")
self.window = builder.get_object("window")
builder.connect_signals(self)
if __name__ == "__main__":
editor = TutorialTextEditor()
editor.window.show()
gtk.main()
命名为
tutorial.py
并保存到
tutorial.xml
所在目录。
Compiling and Running the Application
C 语言
C 是一种编译语言, 需要使用 gcc 编译器把源代码转换为二进制可执行代码。为了让 gcc 知道 GTK+ 链接库位置以及编译标识,我们使用 pkg-config 。当我们安装 GTK+ 开发包时, 一个叫 "gtk +-2.0.pc " 的配置文件也安装了, 它告诉 pkg-config 我们系统上安装的 GTK+ 库版本以及包含文件位置等信息。
输入以下命令; pkg-config --modversion gtk+-2.0
终端输出将是你安装的 GTK+ 版本号。我的系统上显示为 2.12.0 。现在来看编译 GTK+ 应用程序时需要的编译器标识: pkg-config --cflags gtk+-2.0
输出将是一堆的 -I 开关选项指出编译器使用的包含文件。这能让 gcc 知道到哪去找我们应用程序中 "#include" 所列出的包含文件。
每当使用了 "#include" 并引用了非标准 C 库头文件时, 都需要使用 "I/path/to/library" 选项传给 gcc 。这些库可以装在不同的地方,这根据分发要求,操作系统或使用者意愿来定。而 pkg-config 为我们掌控这一切。
编译我们的程序。终端输入以下命令(确保你当前目录为 main.c 和 tutorial.xml 所在目录)
: gcc -Wall -g -o tutorial main.c -export-dynamic `pkg-config --cflags --libs gtk+-2.0`
其中 "-Wall" 选项告诉 gcc 显示警告信息。 "-g" 选项产生调试信息, 当你使用调试器如 gdb 进行单步调试时这非常有用。 " -o tutorial" 告诉 gcc 输出的可执行文件名。 "main.c" 是 gcc 将对其进行编译的源文件。 "-export-dynamic" 关系到我们如何连接信号与回调函数,这在以后讲解。最后出现的是 pkg-config 命令。
注意, pkg-config 命令是用反引号 `` 而不是单引号 '' 。反引号在数字键 1 的旁边。这告诉 shell 先执行 pkg-config --cflags --libs gtk+-2.0 然后将其结果输出复制到 gcc 命令的末尾。 pkg-config 命令可以在任何系统上使用,而不用考虑库的安装位置。
编译之后, 我们可以运行它: ./tutorial
你会看到一些警告信息 "Gtk-WARNING** : Could not find signal handler 'xxxxxx'" , 别担心,这些信息告诉我们在 glade 文件中定义的一些信号在程序中没有相关的处理程序。讲到代码时我在讲解这些。不过你应该看到显示了一个窗口,通过点击 "X" 来关闭它。
如果由于某种原因你不能编译并执行你的程序,可以把错误信息贴到 . 以寻求帮助。
Python 语言
因为 Python 是解释性语言,所以我们不需要编译程序。只是调用 Python 解释器, 源代码中第一行就是做这件事情。为了运行我们的程序, 使用以下命令改变文件访问权限: chmod a+x tutorial.py
然后可运行: ./tutorial.py
Stepping Through the Code
注意:你应该在我讲到某个函数时顺便查阅 GTK+ 开发文档。它会是你最好的朋友。安装 Devhelp 并使用它。考虑到也许你不能安装 Devhelp 的情况,在我讲到一个新函数时将会提供在线文档的链接。
Including the GTK+ Library
C 语言
希望你足够了解了
C
编程并能知道第一行
"#include
Python 语言
希望你足够了解了 Python 编程并能知道前两行 "#import sys" 和 "#import gtk" 是怎么回事。否则,你应该先看看 Python 基础编程向导。现在我们可以访问所有的 gtk.x 类了。
Initializing the GTK+ Library
Python 自动显示初始化了 GTK+ 库, 而 C 你必须在调用任何 GTK+ 函数前初始化 GTK+ 库 gtk_init(&argc , &argv);
Building the Interface with GtkBuilder
在不使用任何辅助 GUI 工具开发 GTK+ 应用程序时, 程序员需要编写程序来创建每个 widgets 并调用相关函数设置其属性,然后装填到容器中。每个步骤都需要许多行代码才能完成, 非常的繁琐。考虑我们在 part1 中建立的用户 GUI 界面, 超过 20 个 widgets (包括菜单项)编写代码来创建这些界面需要将近百行代码才能创建并设置好每一个属性。
幸好我们有 Glade 和 GtkBuilder 。仅仅只需要 2 行代码, GtkBuilder 就能解析 tutorial.xml 文件,创建所有定义的 widgets 并应用其属性,以及建立 widgets 之间包容父子关系。然后,我们就能利用 GtkBuilder 引用 widgets 并控制其行为特性。
C 语言
builder = gtk_builder_new();
gtk_builder_add_from_file(builder, "tutorial.xml", NULL);
第一个变量是在 main ()中定义的 GtkBuilder 类对象指针。我们使用 gtk_builder_new() 来创建实例。所有的 GTK+ 对象都以这种方式创建。
这时 builder 还没有任何 UI 元素,我们使用 gtk_builder_add_from_file() 来解析 XML 文件,并把其内容添加到 builder 对象。此函数的第三个参数我们传递了 NULL ,因为现在不需要使用 GError 。我们没有进行任何异常和错误处理,因此一旦有任何异常或错误出现, 我们的程序只能崩溃。异常与错误处理我们后面再讲。
在调用了 gtk_builder_new() 创建了对象实例之后,所有的其它 gtk_builder_xxx 函数都是以创建好的 builder 对象作为第一个参数。这就是 GTK+ 用 C 实现的面向对象技术。其它所有 GTK+ 对象都是如此方式。
Python 语言
builder=gtk.Builder()
builder.add_from_file("tutorial.xml")
Getting References to Widgets From GtkBuilder
创建好了所有的 widgets 之后我们就可以引用它们了。我们只需要引用一部分,因为其它的已经能很好地完成它们的工作不再需要更多的处理了。例如, GtkVBox ,容纳了菜单,文本编辑框,状态栏,已经完成了布局工作不需要代码处理了。我们可以在应用程序生命期引用任意一个 widgets 并保存在变量中以备用。在此开发向导中我们仅仅需要引用命名为 "window" 的 GtkWindow 对象,以便显示它。
C 语言
Window=GTK_WIDGET(gtk_builder_get_object(builder, "window"));
首先, gtk_builder_get_object() 第一个参数是获取的对象所在的 builder 对象,第二个参数是获取对象的名称, 这个名称必须与我们在 Glade 中 "name" 属性一致。函数返回一个 GObject 对象指针, 保存在 window 变量中。参考文档中对象层次结构指出 GtkWidget 从 GObject 继承, 因此一个 GtkWindow 就是一个 GObject ,也是一个 GtkWidget 。这是 OOP 的基本概念,对于 GTK+ 编程很重要。
因此 GTK_WIDGET() 宏进行类型转换。你可以用转换宏把 GTK+ 的 widgets 转换为它的任意一个子类型, 所有 GTK+ 类都有相应的类型转换宏。 GTK_WIDGET(something) 就如同 (GtkWidget*)something 所起的作用一样。
最后, main ()函数中我们声明一个 GtkWidget 类型的 window 变量而不是 GtkWidget 类型,这纯属是习惯而已。我们也可以把它声明为 GtkWindow* 也对。所有 GTK+ 的 widgets 都继承自 GtkWidget 因此我们可以声明指向任何 widgets 的指针为 GtkWidget 类型。许多函数都传递 GtkWidget* 类型的参数,并且许多函数返回的也是 GtkWidget* 类型指针。所以声明为 GtkWidget 类型然后必要时使用转换宏转换为其它 widgets 类型。
Python 语言
self.window = builder.get_object("window")
Connecting Callback Functions to Signals
在 part1 我们指定了许多信号的处理函数, 当有事件发生时 GTK+ 就发送相应的信号, 这是 GUI 编程的基础概念。我们的应用程序需要知道用户何时做了何事,然后对此进行响应。你将会看到,我们的程序就是循环等待事件的发生。我们使用 GtkBuilder 来连接在 Glade 中定义的信号和相应的回调函数。 GtkBuilder 会自动查询程序符号表然后正确地连接信号与处理函数。
在 part1 中, 我们定义了 "on_window_destroy" 函数来响应 "window" 的 "destroy" 信号。当一个 GObject 对象销毁时,它会发送 "destroy" 信号,我们的应用程序就无限循环等待事件发生, 当用户关闭主窗口(点击主窗口标题栏的 "X" 按钮)应用程序需要能够终止循环并退出。连接一个回调函数到 GtkWindow 的 "destroy" 信号,就能知道何时终止程序。因此, "destroy" 信号是几乎所有的 GTK+ 应用程序中都会处理的。
注意:本实例中用来连接信号与处理程序的函数与 Libglade 中的 glade_xml_signal_autoconnect() 函数等价。
C 语言
gtk_builder_connect_signals(builder, NULL);
此函数总是需要传递 builder 对象作为第一个参数,第二个参数是用户数据。这很有用,不过现在设为 NULL 即可。这个函数会使用 GModule , GLib 的一部分, 动态加载模块来查询应用程序符号表(函数名, 变量名等等),寻找应用程序中能够与 Glade 中指定的回调函数名相符的函数,然后连接到信号。
在 Glade 中我们为 GtkWindow 的 "destroy" 信号指定了回调函数名为 "on_window_destroy" ,因此 gtk_builder_connect_signals() 会在程序中寻找名为 "on_window_destroy" 的处理函数,如果找到则连接到 "signal" 信号。函数原型必须一致才能连接,包括函数名,参数个数类型,返回类型等。 "destroy" 信号属于 Gtk Object 类,因此可以在开发文档中查找 GtkObject 目录下的 "destroy"signal 找到相应的回调函数原型,根据此原型我们可以定义如下处理函数 :
void
on_window_destroy (GtkObject *object, gpointer user_data)
{
gtk_main_quit();
}
现在, gtk_builder_connect_signals() 将会找到它并确认与 Glade 中指定的函数匹配,因此就把此函数与 "destroy" 信号连接。当 GtkWindow 对象 "window" 销毁时将会调用上述函数。此函数仅仅是调用了 gtk_main_quit() 来结束循环并退出应用程序。
因为我们不再使用 GtkBuilder 对象了,所以可以将其销毁并释放为 XML 文件分配的空间: g_object_unref(G_OBJECT(builder)) 。
你注意到我们使用 G_OBJECT 宏将 GtkBuilder* 转换为 GOblet* , 这是必须的因为函数 g_object_unref() 接受 GOblet* 类型参数。而 GtkBuilder 是从 GOblet 继承的。
Python 语言
builder.connect_signals(self)
Showing the Application Windo w
在进入 GTK+ 主循环之前,显示我们的 GtkWindow 类 widget ,否则它是不可见的。
C 语言
gtk_widget_show(window);
此函数设置了 widgets 的 GTK_VISIBLE 标识,告诉 GTK+ 可以显示此 widget 了。
Python 语言
editor.window.show()
Entering the GTK+ Main Loop
GTK+ 的主循环是一个无限循环,一旦创建了 GUI 并设置好了应用程序,就可以进入主循环等待事件的发生。在主循环中发生了很多魔幻的事情,作为一个新手,可以简单的把它看成是一个循环,做了诸如检查状态,更新 UI ,为事件发送信号等事情。
一旦进入主循环,我们的应用程序就不做任何事了( GTK+ 在做),当用户缩放窗口,最小化,点击,按键等等时, GTK+ 检查每一个事件并发送相应的信号。不过,我们的应用程序仅仅对 "window" 的 "destroy" 信号进行响应。其它一概不管。
C 语言
gtk_main();
Python 语言
gtk.main()
总结 :
1 应用程序使用 GtkBuilder 从 XML 文件创建 GUI
2 应用程序得到主窗口小部件的引用
3 应用程序把 "on_window_destroy" 处理函数连接到 "destroy" 信号
4 应用程序显示窗口
5 应用程序进入 GTK+ 主循环
6 用户点击 "X" 按钮关闭窗口,导致 GTK+ 主循环发送 "destroy" 信号
7 信号处理函数 "on_window_destroy" 退出 GTK+ 主循环
8 应用程序正常终结
What's Next?
在接下来的部分, 我将会完成我们的 GTK+ 文本编辑器余留的功能,并处理所需的信号。对代码不再详细阐述。