原文链接:
Micah Carrick
www.micahcarrick.com/01-01-2008/gtk-glade-tutorial-part-3.html
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有一些有用的插件可以使用。我写了一个
Gedit Symbol Browser Plugin 可以让你快速地跳转到源代码的函数定义。
你需要的开发库取决于你使用C还是使用Python, 对于不同的开发平台及发布平台会有很大不同, 不过我会对此提供一些有用的信息。如果在安装开发包和发布产品时还出现问题的话, 可以到GTK+ Forums 寻找答案。
在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 to GtkBuilder F.A.Q..
使用如下命令来把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 <gtk/gtk.h>
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"来关闭它。
如果由于某种原因你不能编译并执行你的程序,可以把错误信息贴到 GTK+ Forums. 以寻求帮助。
Python语言
因为Python是解释性语言,所以我们不需要编译程序。只是调用Python解释器, 源代码中第一行就是做这件事情。为了运行我们的程序, 使用以下命令改变文件访问权限:chmod a+x tutorial.py
然后可运行:./tutorial.py
Stepping Through the Code
注意:你应该在我讲到某个函数时顺便查阅GTK+开发文档。它会是你最好的朋友。安装Devhelp并使用它。考虑到也许你不能安装Devhelp的情况,在我讲到一个新函数时将会提供在线文档的链接。
Including the GTK+ Library
C语言
希望你足够了解了C编程并能知道第一行"#include <gtk/gtk.h>"是怎么回事。否则,你应该先看看C基础编程向导。包含了gtk.h,我们就同时间接地包含了多需的其它头文件。事实上, 我们包含了所有GTK+库及其依赖的GLib库部分头文件。想知道具体有哪些,打开此文件看一看!
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"信号属于GtkObject类,因此可以在开发文档中查找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 Window
在进入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+文本编辑器余留的功能,并处理所需的信号。对代码不再详细阐述。