使用 Python 和 Glade 进行快速应用程序开发

作者:David Reed

编写 GUI 程序包括两个基本步骤。首先,您需要编写代码来创建界面,包含菜单等元素和按钮、标签和输入字段等控件。然后,您需要编写在事件发生时执行的代码,例如当按钮被按下或菜单项被选中时。当程序运行时,它进入一个事件循环,重复等待事件,然后调用为该事件定义的事件处理程序,也称为回调函数。例如,您可以编写一个函数,在按钮被按下时调用。手动编写代码来显示控件、定义在事件发生时调用的函数以及将每个事件连接到特定函数是一个繁琐的过程。

对于许多控件集,都存在程序可以可视化地布局 GUI。Damon Chaplin 编写了 Glade 程序,允许用户使用 GTK/GNOME 控件以可视化方式创建界面,并指定在事件发生时要调用的函数。Glade 将控件的布局和回调存储为 XML 文件。Glade 还生成一个 C 或 C++ 程序,其中包含创建指定布局中的控件、连接回调以及为每个回调定义空函数的所有调用。但是,Glade 不创建 Python 代码。我编写的 GladeGen 程序根据 Glade XML 文件生成 Python 代码。

如果您使用 Glade 更改 GUI,则需要 Glade 输出新的 C/C++ 代码来创建 GUI。这可能会很烦人,特别是如果您修改了创建 GUI 的代码。James Henstridge 编写了 libglade,以减轻在程序中硬编码 GUI 生成代码的需求。James 还启动并帮助维护 GTK/GNOME Python 绑定,这是一个提供对 GTK/GNOME C 函数访问的 Python 模块。使用 libglade,您的程序不包含创建界面的代码。相反,libglade 在程序运行时解析 XML 文件,并在运行时动态创建界面。因此,每当您使用 Glade 更改 GUI 时,您都不需要更改创建界面的代码。

除了在需要高速运算的大量计算代码的情况下,我更喜欢使用 Python。Python 的高级数据结构和解释性环境使其能够快速开发、修改和维护代码。《Linux Journal》2003 年 9 月刊包含一篇关于 PyGTK 和 Glade 的介绍性文章(请参阅在线资源部分),不熟悉 Gtk 和 Glade 的读者会觉得它很有帮助。

GladeGen 的动机是我为我妻子工作的验光/光学诊所编写的患者数据库/会计系统。在我妻子开始与他们合作之前,他们使用的是某人编写的 Microsoft Access 数据库系统。那个人已经搬出了州,办公室想要一个新的系统。他们与镇上其他光学诊所交谈,了解正在使用的软件。每个诊所的人都抱怨该软件;系统存在漏洞且价格昂贵。我说服了我妻子的诊所的所有者,我可以在夏天为他们编写一个定制系统,成本与其他新软件大致相同,并且它将完全满足他们的需求。但是,他必须让我使用 Linux。

最终结果是一个 Python/GTK 程序,它使用 PostgreSQL 作为后端数据库来存储所有数据。这允许所有搜索和制表都由 SQL 命令完成。Python 代码提供了在 PostgreSQL 数据库和 GTK 界面之间进行通信的代码层。GTK 和 PostgreSQL 都是用 C 编写的,因此它们运行速度很快。在现代处理器上,Python 代码对于处理 GTK 和 PostgreSQL 之间的通信来说已经足够快了。PyGTK 前端和 PostgreSQL 数据库允许客户端前端访问任何数据库服务器,因此他们可以在多台计算机上运行前端 GUI 并访问数据库服务器。客户端/服务器设置允许他们在其他位置通过 SSH 加密连接在 Internet 上运行前端 GUI 并访问 PostgreSQL 服务器。它还允许我在用户对系统有疑问时从家中进行远程访问。

该程序有 40 多个窗口,包括用于输入患者信息、镜框/镜片购买、隐形眼镜、核对保险付款以及输入和跟踪镜框库存的窗口。我决定使用 Glade 创建每个窗口,并且因为我想使用 Python,所以我需要编写自己的软件来自动化每个窗口的代码创建。结果就是 GladeGen。

GladeGen 用法

我编写的代码自动化了创建 Python 代码的任务,以根据 Glade XML 文件创建界面、连接回调函数、提供对控件的访问以及创建空回调函数。使用此软件,创建 GUI 程序的步骤是:1) 使用 Glade 以可视化方式创建界面并保存 XML 文件,2) 运行 GladeGen 生成代码,以及 3) 编写回调的代码。

GladeGen 创建的代码包含运行应用程序和显示界面的所有代码,以及空回调函数。它还允许您使用 Glade 修改界面。当您重新运行 GladeGen 时,它会重新生成应用程序代码,其中包含任何其他回调和新控件,而不会更改或修改任何现有代码。

在这里,我演示如何通过创建一个数学测验程序来使用 GladeGen。完整的程序可以从《Linux Journal》FTP 站点获得(请参阅资源)。GladeGen 适用于 GTK 2.x 控件集和相应版本的 Glade,在 Red Hat 系统上,该版本名为 glade-2。如果您熟悉 GTK 控件,Glade 使用起来相当直观。如果不熟悉,您应该熟悉各种容器控件,包括表格和水平/垂直框。

使用 glade-2,我创建了界面的第一个版本。我从 GtkWindow 开始,并添加了一个 GtkVBox。我在垂直框中放置了两个 GtkFrame 控件,并在每个框架中放置了一个 GtkTable。所有其他控件都放置在两个表格控件中。我使用 GtkSpinButton 控件允许用户选择问题中的位数和运算符。GtkCheckButton 控件用于指示问题中应包含哪些运算符。GtkEntry 控件用于问题、答案以及有关回答正确/错误问题数量的信息。其他控件是 GtkLabel 和 GtkButton 控件。

我将 Glade 文件保存为 mathflash.glade。Glade 不会询问您是否要保存文件,因此如果您对界面进行任何更改,您需要记住在退出之前保存它。有关界面和 Glade 的屏幕截图,请参见图 1。它显示 reset_button 配置为在单击按钮时调用函数 on_reset_button_clicked。

Rapid Application Development with Python and Glade

图 1. 在 Glade 中设置按钮的回调

Glade 允许您为每个控件命名或提供默认名称。对于我们需要与之交互的控件,例如按钮和数据输入控件,我提供了与其预期用途匹配的名称。使用 Glade,您还可以指定要连接到回调函数的信号。如上所述,使用 Glade,我指定在单击 reset_button 时应调用 on_reset_button。

接下来,我使用 GladeGen 生成应用程序的模板代码,使用命令GladeGen.py mathflash.glade MathFlash MathFlash。命令行参数是 Glade XML 文件的名称、要创建的 Python 文件/模块的名称以及要在该文件/模块中创建的类的名称。生成的 MathFlash.py 文件可以在清单 1 中找到。MathFlash 类继承了 GladeWindow 类,该类使用 libglade 连接 handlers 变量中列出的所有回调。它还创建一个字典 self.widgets,将 widget_list 变量中的每个控件名称映射到相应的 GtkWidget 实例。GladeWindow 子类提供默认的 show 和 hide 方法,以便可以立即运行模板代码以在开始编写回调之前查看界面。

清单 1. GladeGen 从在 Glade 中创建的 XML 文件生成了此 Python 代码。

#!/usr/bin/env python

#--------------------------------------------
# MathFlash.py
# Dave Reed
# 02/28/2004
#--------------------------------------------

import sys

from GladeWindow import *

#--------------------------------------------

class MathFlash(GladeWindow):

    #----------------------------------------

    def __init__(self):

        ''' '''

        self.init()

    #----------------------------------------

    def init(self):

        filename = 'mathflash.glade'

        widget_list = [
            'window1',
            'plus_check',
            'minus_check',
            'multiply_check',
            'divide_check',
            'digits_spin',
            'operators_spin',
            'correct_entry',
            'wrong_entry',
            'pct_entry',
            'problem_entry',
            'answer_entry',
            'submit_button',
            'reset_button',
            'exit_button',
            'new_button',
            'result_entry',
            ]

        handlers = [
            'on_operator_check_toggled',
            'on_submit_button_clicked',
            'on_reset_button_clicked',
            'on_exit_button_clicked',
            'on_new_button_clicked',
            ]

        top_window = 'window1'
        GladeWindow.__init__(self, filename,
                             top_window, widget_list,
                             handlers)

    #----------------------------------------

    def on_operator_check_toggled(self, *args):
        pass

    def on_submit_button_clicked(self, *args):
        pass

    def on_reset_button_clicked(self, *args):
        pass

    def on_exit_button_clicked(self, *args):
        pass

    def on_new_button_clicked(self, *args):
        pass

#----------------------------------------

def main(argv):

    w = MathFlash()
    w.show()
    gtk.main()

#----------------------------------------

if __name__ == '__main__':
    main(sys.argv)

GladeGenConfig.py 文件允许自定义以指定程序作者、您要放入 self.widgets 字典中的控件类型以及创建的 Python 代码文件应如何显示。在提供的 GladeGenConfig.py 文件中,列出了控件类型 GtkWindow、GtkButton、GtkSpinButton、GtkCheckButton、GtkEntry、GtkCombo 和 GtkTextView。很少需要访问 GtkLabel 控件和容器控件,因此我没有将它们包含在 GladeGenConfig 中的 include_widget_types 列表中。

创建的 MathFlash.py 文件包含每个回调函数的方法,其中包含 Python 空操作语句 pass。该方法使用 self 参数和 *args 声明,用于可变长度参数列表。在 Python 中,*args 允许将任意数量的参数传递给函数/方法,并且它们在调用函数中作为元组接收。大多数回调都传递了事件发生的控件,有时还传递了特定于事件的其他参数。GTK 网站上提供的 API 参考显示了每个回调的确切参数(请参阅资源)。现在,我们需要向回调添加代码以及程序需要的任何其他代码。对于此程序,大约 60 行额外的 Python 代码产生了最终的 MathFlash.py 文件。经验丰富的 Glade 用户和 Python 程序员应该能够在 30 分钟内从头开始创建整个程序。对于更复杂的程序,可以使用模型/视图/控制器设计模式。GladeGen 生成的代码将扮演控制器的角色。

让我们检查回调 on_submit_button_clicked,以了解如何使用 GladeGen 生成的 PyGTK 代码的详细信息。这是我编写的 Python 代码

def on_submit_button_clicked(self, *args):

    prob = self.widgets['problem_entry'].get_text()
    ans = eval(prob)
    user = int(self.widgets['answer_entry'].get_text())
    self.total += 1
    if ans == user:
        self.correct += 1
        self.widgets['result_entry'].set_text('Correct')
    else:
        self.widgets['result_entry'].set_text(
            'Wrong, the answer is %d' % ans)
    self.show_results()

C GTK API 包含函数 G_CONST_RETURN gchar* gtk_entry_get_text(GtkEntry *entry)。因为 Python 是一种面向对象的语言,所以绑定被设置为类的方法。对于 Python 绑定,函数名称中的 gtk_ 和控件名称前缀被删除,并使用该控件的 Python 实例调用。此模式适用于所有 GTK 函数。使用 self.widgets 实例,相应的 Python 调用变为self.widgets['problem_entry'].get_text(),其中 problem_entry 是 Glade XML 文件中输入控件的名称。

然后,我使用 Python eval 函数来确定问题的答案。这比编写我自己的解析器来评估表达式要简单得多。乘法和除法的优先级高于加法和减法的常用规则适用。如果您允许用户输入要评估的字符串,则使用 eval 函数可能存在安全问题,但在这种情况下,我们的程序正在生成要评估的字符串,因此我们无需担心它。

如果您修改 Glade XML 文件,您可以重新运行 GladeGen,它将添加任何新的回调方法,而不会覆盖或丢失您编写的任何代码,但 init 方法除外,您永远不应修改 init 方法。GladeGen 每次运行时都会生成一个新的 init 方法。init 方法包含控件和回调的名称,因此每当 Glade 文件更新时,都需要重新生成它。在我的例子中,我后来添加了一个按钮来重置统计信息。当我重新运行 GladeGen 时,它添加了一个空的 on_reset_button_clicked 方法,并提供了一个新的 init 方法,其中列出了 reset_button 控件和 on_reset_button_clicked 回调。因为我使用 libglade 在运行时创建界面,所以不需要进行额外的修改。

GladeGen 的工作原理

GladeGen 首先确定指定的模块/文件是否存在;如果不存在,它会创建一个包含作者信息和类定义的基本文件。然后,GladeGen 解析 Glade XML 文件并找到控件和处理程序的列表。Python 提供了 inspect 模块,该模块允许程序确定现有 Python 模块包含哪些函数、类和方法,以及哪些行对应于每个函数、类和方法。GladeGen 使用 inspect 模块来确定哪些回调已经被编写,以便它们不会被空回调方法替换。GladeGen 将任何新的回调添加到类定义的底部。inspect 模块还允许 GladeGen 确定哪些行包含 init 方法,并将它们替换为包含最新 Glade XML 文件中的所有控件和处理程序的新 init 方法。

Python 支持用于解析 XML 文件的标准 DOM 和 SAX 接口。SAX 接口是一个事件驱动模型,其中用户设置要调用的函数,因为 XML 标记被处理。DOM 接口将整个 XML 文件读入内存,并提供用于遍历 XML 层次结构和检索信息的函数。对于 GladeGen,我们只想从 XML 文件中提取某些信息,因此 DOM 接口更易于使用。此外,Glade XML 文件的大小足够小,以至于将整个文件读入内存并生成其 Python 表示形式不应需要大量内存。使用 DOM 接口,GladeGen 类中的 get_xml 方法使用大约 30 行 Python 代码从 Glade XML 文件中提取控件名称和处理程序名称。

总结

Glade 和 GladeGen 自动化了创建图形程序的大部分繁琐、重复性工作,从而无需编写代码来创建和存储控件以及设置回调函数。这允许快速开发 Python/GNOME/GTK 应用程序。完成的数学闪卡如图 2 所示。GladeGen 软件可以在任何支持 Python 和 GTK 的系统上运行,包括 Linux、UNIX、Mac OS X 和 Microsoft Windows。

Rapid Application Development with Python and Glade

图 2. 数学闪卡

可以向此系统添加许多功能。可以根据控件和回调原型显式指定为创建的回调函数使用通用 *args 参数,而不是使用通用 *args 参数。我还计划为程序添加一个图形前端,用于配置 GladeGenConfig.py 文件中的选项。GladeGen 软件在 GPL 下发布。如果有人有兴趣修改/扩展它,请告知作者。

致谢

感谢我的学生之一 Jeremiah Schilens,他与我一起参与了该项目的早期版本。

本文的资源: /article/7558

David Reed 与他的妻子和两条狗住在俄亥俄州哥伦布市。自 1991 年以来,他一直从事 UNIX 系统工作,自 1997 年以来一直从事 Linux 系统工作。他拥有俄亥俄州立大学的体绘制图形学博士学位,目前在首都大学教授计算机科学。首都大学在其 CS 课程中混合使用了 Python、C++ 和 Java。可以通过 dreed@capital.edu 联系 David。

加载 Disqus 评论