pyGTK 和 Glade 初学者指南

作者:Dave Aitel

pyGTK 和 Glade 的美妙之处在于,它们为我们这些宁愿做其他事情,但仍然需要在所有事情之上使用 GUI 的人,开启了跨平台的专业级 GUI 开发。pyGTK 不仅允许新手创建出色的 GUI,还允许专业人士比以往更快地创建灵活、动态且功能强大的用户界面。如果您曾经想创建一个外观精美且无需大量工作,并且您没有任何 GUI 经验的快速用户界面,请继续阅读。

本文是在编写 Immunity CANVAS (www.immunitysec.com/CANVAS) 时学习过程的直接结果。从头开始开发 GUI 时学到的许多东西都放在了 pyGTK FAQ 中,该 FAQ 位于 www.async.com.br/faq/pygtk/index.py?req=index。如果您深入研究 pyGTK,毫无疑问您会经常使用的另一个 URL 是 www.gnome.org/~james/pygtk-docs 上的文档。 可以公平地说,对于一家小公司而言,使用 pyGTK 而不是其他 GUI 开发环境(例如原生 C)是一种竞争优势。希望在阅读本文之后,每个人都应该能够使用 Python(所有语言中最容易学习的语言)组合出一个 GUI。

作为一个指标,CANVAS GUI 是从头开始编写的,大约花了两个星期,事先没有任何 pyGTK 知识。然后,它在一天之内从 GTK v1 移植到 GTK v2(稍后会详细介绍),现在已部署给 Microsoft Windows 和 Linux 客户。

pyGTK 的跨平台特性

在一个完美的世界中,您永远不必为除运行您最喜欢的发行版的 Linux 之外的任何东西进行开发。在现实世界中,您需要支持多个版本的 Linux、Windows、UNIX 或客户需要的任何其他平台。选择 GUI 工具包取决于您的客户平台上哪些工具包得到良好支持。如今,如果开发速度比运行时速度更重要,那么选择 Python 作为您任何新事业的开发工具已成为第二天性。这种组合使您可以从以下 Python GUI 开发替代方案中进行选择:wxPython、Tkinter、pyGTK 和 Python/Qt。

请记住,我不是专业的 GUI 开发人员,以下是我认为应该选择 pyGTK 的原因。wxPython 已经取得了长足的进步,并提供了有吸引力的界面,但很难使用和使其工作,尤其是对于初学者而言。更不用说,它要求 Linux 和 Windows 用户都下载并安装大型二进制软件包。Qt 虽然对于 Linux 是免费的,但需要在 Windows 上分发许可证。这对于许多想要在多个平台上分发的小公司来说可能是令人望而却步的。

Tkinter 是第一个 Python GUI 开发工具包,几乎每个 Python 发行版都提供它。但是,它看起来很丑陋,并且需要您将 Tk 嵌入到您的 Python 应用程序中,这感觉就像在倒退。对于初学者来说,您真的希望尽可能地将 GUI 与应用程序分离。这样,当您编辑 GUI 时,您不必更改应用程序中的一堆内容或将任何更改集成到您的应用程序中。

仅凭这些原因,pyGTK 可能是您的选择。它巧妙地将应用程序与 GUI 分离。使用 libglade,GUI 本身以 XML 文件的形式保存,您可以继续编辑、保存多个版本或任何您想要的其他操作,因为它没有与您的应用程序代码集成。此外,使用 Glade 作为 GUI 构建器允许您快速创建应用程序界面——速度如此之快,以至于如果多个客户想要多个 GUI,您都可以轻松支持它们。

GTK 和 pyGTK 的版本问题

在野外有两种主要的 GTK 版本:GTK 版本 1 和 2。因此,在 GUI 构建项目开始时,您必须就开发和维护的内容做出一些选择。您的机器上可能已安装 Glade v1。您可能需要下载 Glade v2 或安装 GTK 的开发包以编译 GTK v2 libglade。相信我,这是值得的。GTK v2 提供了几个优点,包括更美观的整体外观、适用于 Windows 和 Python 2.2 的安装程序以及允许为盲人用户定制应用程序的辅助功能扩展。此外,版本 2 已安装在许多最新的发行版中,尽管您可能仍然需要安装开发 RPM 或最新的 pyGTK 软件包。

GTK v2 以及 pyGTK v2 提供了一些稍微更复杂的窗口小部件(视图)。在强大的 GUI 大师手中,它们可以产生令人敬畏的应用程序,但它们真的会让初学者感到困惑。但是,一些代码示例意味着您可以像对待 GTK v1 中的对应项一样对待它们,一旦您学会如何使用它们。

例如,在 GTK v1 中开发了 CANVAS 的整个 GUI 之后,我不得不返回并在 GTK v2 中重新开发它(这恰好花了一天时间)。我的客户的 Linux 机器中缺少对 GTK v1 的支持,但安装 GTK v2 很容易。主要的例外是 Ximian Desktop,它使 pyGTK 和 GTK v1 易于安装。因此,如果您的整个客户群都在运行它,您可能希望继续使用 GTK v1。但请记住一件事——有一个 Python 脚本可用于将项目从 Glade v1 转换为 Glade v2,但反之则不然。因此,如果您要同时进行这两项操作,请先在 Glade v1 中开发它,转换它,然后协调任何差异。

Glade v2 简介

使用 Glade 和 libglade 背后的理论是,使用代码创建 GUI 会浪费时间。坐下来告诉 Python 解释器每个小部件的位置、颜色以及默认值是一个巨大的时间消耗。任何在 Tcl/Tk 中编程过的人都花费了数天的时间来做这件事。不仅如此,有时更改使用代码创建的 GUI 可能是一项艰巨的任务。使用 Glade 和 libglade,您无需创建代码,而是创建 XML 文件和代码链接到这些文件,无论按钮、输入框或输出文本缓冲区位于何处。

首先,如果您还没有 Glade v2,则需要它。即使您有,您也可能需要它的最新版本。一旦您安装了 GTK v2 开发包(-devel RPM),下载和安装 Glade v2 应该很容易。但是,对于大多数 GUI 开发新手来说,Glade 的起始窗口非常空白,令人望而生畏。

要开始您的应用程序,请单击窗口图标。现在,您的屏幕上应该有一个大的空白窗口(图 1)。

A Beginner's Guide to Using pyGTK and Glade

图 1. 起始窗口中的交叉阴影区域是放置另一个小部件的位置。

关于 GUI 开发,需要学习的重要一点是,基本上有两种类型的对象:小部件,例如标签和输入框以及您可以看到的其他东西,以及这些小部件的容器。最有可能的是,您将使用三种容器之一:垂直框、水平框或表格。要创建复杂的布局,最简单的方法是将这些容器嵌套在一起,无论您需要什么顺序。例如,单击水平框图标。单击 window1 中的阴影区域会插入三个更多区域,您可以在其中添加小部件。您的新 window1 应该如图 2 所示。

A Beginner's Guide to Using pyGTK and Glade

图 2. 基本的三窗格 vbox,顶部窗格已选中。

现在您可以选择这三个区域中的任何一个,并使用垂直框进一步划分它。如果您不喜欢结果,您可以随时返回并从“属性”菜单中删除、剪切和粘贴或更改框的数量(稍后会详细介绍)。

A Beginner's Guide to Using pyGTK and Glade

图 3. 顶部窗格已被两窗格 hbox 分割,该 hbox 已选中。

您可以使用这些类型的基元来创建几乎任何类型的布局。现在我们有了一个起始布局,我们可以用实际执行某些操作的小部件填充它。在本例中,我将用标签、文本输入框、微调按钮和按钮填充它们。起初,这看起来很丑陋(图 4)。

A Beginner's Guide to Using pyGTK and Glade

图 4. 用小部件填充的初始窗口。

请记住,GTK 会在显示时自动调整最终产品的大小,因此所有内容都尽可能紧密地打包在一起。当用户拖动窗口的角时,它也会自动展开。您可以在“属性”窗口中调整这些设置(转到主 Glade 窗口并单击“视图”→“显示属性”)。“属性”窗口会更改不同类型小部件的不同值。例如,如果微调按钮处于焦点,我们会看到图 5 中显示的选项。

A Beginner's Guide to Using pyGTK and Glade

图 5. 用于更改小部件属性的 Glade 界面针对每种类型的小部件进行了自定义。

通过更改“值”选项,我们可以更改微调按钮在显示时的默认值。同样重要的是更改“最大值”。一个常见的错误是将“值”更改为较高的值,但忘记了“最大值”,这会导致微调按钮最初显示默认值,然后在更改时恢复为“最大值”,从而使用户感到困惑。在我们的例子中,我们将使用微调按钮作为 TCP 端口,因此我将其设置为 65535,最小值为 1,默认值为 80。

然后,将焦点放在 label1 上,并将其更改为读取“主机:”。通过单击主 Glade 窗口中的 window1,您可以将焦点放在整个窗口上,从而允许您也更改其属性。您也可以通过调出小部件树窗口并单击 window1 来做到这一点。将名称更改为 serverinfo,并将标题更改为“服务器信息”可以为该应用程序适当地设置标题栏和内部 Glade 顶级小部件名称。

如果您转到小部件树视图并单击 hbox1,您可以增加“主机:”和文本输入框之间的间距。这可能会使它看起来更漂亮一些。我们完成的 GUI 如图 6 所示。

A Beginner's Guide to Using pyGTK and Glade

图 6. Glade 中的 GUI 看起来与渲染后的效果不太一样,因此不必担心“主机:”区域的大小。

通常,这只需要几分钟即可完成。经过一些练习,您会发现,即使使用 Glade 组合最复杂的 GUI 也可以在几分钟内完成。将其与手动键入所有 Tk 命令来完成相同操作所花费的时间进行比较。

当然,这个 GUI 现在还什么都不做。我们需要编写 Python 代码来加载 .glade 文件并完成实际工作。实际上,我倾向于为每个 Glade 驱动的项目编写两个 Python 文件。一个文件处理 GUI,另一个文件对 GUI 一无所知。这样,从 GTK v1 移植到 GTK v2 甚至另一个 GUI 工具包都很容易。

创建 Python 程序

首先,我们需要处理任何潜在的版本偏差。我使用以下代码,尽管 FAQ 中提到的其他一些条目也做了类似的事情

#!/usr/bin/env python

import sys

try:
 import pygtk
  #tell pyGTK, if possible, that we want GTKv2
  pygtk.require("2.0")
except:
  #Some distributions come with GTK2, but not pyGTK
  pass

try:
  import gtk
  import gtk.glade
except:
  print "You need to install pyGTK or GTKv2 ",
  print "or set your PYTHONPATH correctly."
  print "try: export PYTHONPATH=",
  print "/usr/local/lib/python2.2/site-packages/"
  sys.exit(1)

#now we have both gtk and gtk.glade imported
#Also, we know we are running GTK v2

现在我们将创建一个名为 appGUI 的 GUI 类。但在我们这样做之前,我们需要打开 button1 的属性并添加一个信号。为此,请单击三个点,滚动到“clicked”,选择它,然后单击“添加”。您应该得到如图 7 所示的结果。

A Beginner's Guide to Using pyGTK and Glade

图 7. 添加事件(信号)处理程序后

有了这个,signal_autoconnect 会导致任何按钮单击都调用我们的一个函数 (button1_clicked)。您也可以在该列表中看到要处理的其他潜在信号。每个小部件可能具有不同的潜在信号。例如,捕获文本输入小部件上的 text-changed 信号可能很有用,但按钮永远不会更改,因为它不可编辑。

初始化应用程序并启动 gtk.mainloop() 使球开始滚动。不同的事件处理程序需要有不同数量的参数。clicked 事件处理程序只获得一个参数,即被单击的小部件。在您进行操作时,将 destroy 事件添加到主窗口,以便在您关闭窗口时程序退出。不要忘记保存您的 Glade 项目。

class appgui:
  def __init__(self):
    """
    In this init we are going to display the main
    serverinfo window
    """
    gladefile="project1.glade"
    windowname="serverinfo"
    self.wTree=gtk.glade.XML (gladefile,windowname)
    # we only have two callbacks to register, but
    # you could register any number, or use a
    # special class that automatically
    # registers all callbacks. If you wanted to pass
    # an argument, you would use a tuple like this:
    # dic = { "on button1_clicked" : \
             (self.button1_clicked, arg1,arg2) , ...

dic = { "on_button1_clicked" : \
            self.button1_clicked,
            "on_serverinfo_destroy" : \
            (gtk.mainquit) }
    self.wTree.signal_autoconnect (dic)
    return

#####CALLBACKS
  def button1_clicked(self,widget):
    print "button clicked"

# we start the app like this...
app=appgui()
gtk.mainloop()

如果您是从源代码安装 pyGTK 的,请务必将 PYTHONPATH 环境变量设置为指向 /usr/local/lib/python2.2/site-packages/,以便可以正确找到 pyGTK。另外,请确保将 project1.glade 复制到当前目录中。当您运行新程序时,您应该得到如图 8 所示的结果。单击“GO!”应该在您的终端窗口中生成一条漂亮的 button-clicked 消息。

A Beginner's Guide to Using pyGTK and Glade

图 8. 初始服务器信息 GUI

为了使应用程序实际执行一些有趣的操作,您需要某种方法来确定要使用哪个主机和哪个端口。以下代码片段放入 button1_clicked() 函数中应该可以解决问题

host=self.wTree.get_widget("entry1").get_text()
port=int(self.wTree.get_widget(
  "spinbutton1").get_value())
if host=="":
  return
import urllib
page=urllib.urlopen(
  "http://"+host+":"+str(port)+"/")
data=page.read()
print data

现在,当单击“GO!”时,您的程序应该前往远程站点,抓取网页并将内容打印在终端窗口中。您可以通过向 hbox 添加更多行并将其他小部件(如菜单栏)放入应用程序中来使其更精彩。您还可以尝试使用表格而不是嵌套的 hboxes 和 vboxes 进行布局,这通常会创建外观更漂亮的布局,其中所有内容都对齐。

TextViews

但是,您真的不希望所有这些文本都转到终端,对吗?您可能希望将其显示在另一个小部件甚至另一个窗口中。要在 GTK v2 中执行此操作,请使用 TextView 和 TextBuffer 小部件。GTK v1 有一个易于理解的小部件,简称为 GtkText。

将 TextView 添加到您的 Glade 项目,并将结果放在该窗口中。您会注意到创建了一个 scrolledwindow 来封装它。将以下行添加到您的 init() 中以创建 TextBuffer 并将其附加到您的 TextView。显然,GTK v2 方式的一个优点是两个不同的视图可以显示相同的缓冲区。您可能还想进入 scrolledwindow1 的“属性”窗口,并将大小设置为更大的值,以便您有一个合适的查看空间

self.logwindowview=self.wTree.get_widget("textview1")
self.logwindow=gtk.TextBuffer(None)
self.logwindowview.set_buffer(self.logwindow)

在您的 button1_clicked() 函数中,将 print 语句替换为

self.logwindow.insert_at_cursor(data,len(data))

现在,每当您单击“GO!”时,结果都会显示在您的窗口中。通过用一组垂直窗格划分主窗口,您可以根据需要调整此窗口的大小(图 9)。

A Beginner's Guide to Using pyGTK and Glade

图 9. 单击“GO!”加载网页并将其显示在 TextView 中。

TreeViews 和 Lists

与 GTK v1 不同,在 GTK v2 下,树和列表基本上是同一回事;区别在于它们各自使用的存储类型。另一个重要的概念是 TreeIter,它是一种用于存储指向树或列表中特定行的指针的数据类型。它本身不提供任何有用的方法,也就是说,您不能 ++ 它来逐步遍历树或列表的行。但是,当您想要引用树中的特定位置时,它会传递到 TreeView 方法中。所以,例如

import gobject
self.treeview=[2]self.wTree.get_widget("treeview1")
self.treemodel=gtk.TreeStore(gobject.TYPE_STRING,
                             gobject.TYPE_STRING)
self.treeview.set_model(self.treemodel)

定义了一个具有两列的树模型,每列包含一个字符串。以下代码向列的顶部添加了一些标题

self.treeview.set_headers_visible(gtk.TRUE)
renderer=gtk.CellRendererText()
column=gtk.TreeViewColumn("Name",renderer, text=0)
column.set_resizable(gtk.TRUE)
self.treeview.append_column(column)
renderer=gtk.CellRendererText()

column=gtk.TreeViewColumn("Description",renderer,
                          text=1)
column.set_resizable(gtk.TRUE)
self.treeview.append_column(column)
self.treeview.show()

您可以使用以下函数手动将数据添加到您的树中

def insert_row(model,parent,
               firstcolumn,secondcolumn):
    myiter=model.insert_after(parent,None)
    model.set_value(myiter,0,firstcolumn)
    model.set_value(myiter,1,secondcolumn)
    return myiter

这是一个使用此函数的示例。不要忘记将 treeview1 添加到您的 glade 文件,保存它并将其复制到您的本地目录

model=self.treemodel
insert_row(model,None,'Helium',
           'Control Current Helium')
syscallIter=insert_row(model,None,
                    'Syscall Redirection',
                    'Control Current Syscall Proxy')
insert_row(model,syscallIter,'Syscall-shell',
           'Pop-up a syscall-shell')

图 10 中的屏幕截图显示了结果。您可以看到,我已经用 TreeView 替换了 TextView。

A Beginner's Guide to Using pyGTK and Glade

图 10. 具有两列的示例 TreeView

列表的完成方式相同,只是您使用 ListStore 而不是 TreeStore。此外,最有可能的是您将使用 ListStore.append() 而不是 insert_after()。

使用对话框

对话框与普通窗口在一个重要方面有所不同——它返回值。要创建对话框,请单击对话框按钮并命名它。然后,在您的代码中,使用以下命令渲染它[3]gtk.glade.XML(gladefile,dialogboxname)。然后调用 get_widget(dialogboxname) 以获取该特定小部件的句柄,并调用其 run() 方法。如果结果是 gtk.RESPONSE_OK,则用户单击了“确定”。否则,用户关闭了窗口或单击了“取消”。无论哪种方式,您都可以 destroy() 小部件使其消失。

使用对话框时的一个问题是:如果在您调用 widget.destroy() 之前发生异常,则无响应的对话框可能会挂起,从而使用户感到困惑。在您收到响应并从窗口小部件中的任何输入框中收到所需的所有数据后,立即调用 widget.destroy()。

使用 input_add() 和 gtk.mainiteration() 处理套接字

有一天,您可能会编写一个使用套接字的 pyGTK 应用程序。执行此操作时,请注意,在处理事件时,应用程序不会执行任何其他操作。例如,在等待 socket.accept() 时,您将卡住并看到一个无响应的应用程序。相反,请使用 gtk.input_add() 将任何可能具有读取事件的套接字添加到 GTK 的内部列表中。这允许您指定一个回调来处理通过套接字传入的任何数据。

这样做时的一个问题是,您通常希望在事件期间更新窗口,这需要调用 gtk.mainiteration()。但是,如果您在 gtk.mainiteration() 中调用 gtk.mainiteration(),则应用程序会冻结。我对 CANVAS 的解决方案是将对 gtk.mainiteration() 的任何调用包装在一个检查中,以确保我没有递归。我检查是否有待处理的事件,例如套接字 accept(),每当我写入日志消息时。我的日志函数最终看起来像这样

def log(self,message,color):
"""
       logs a message to the log window
        right now it just ignores the color
        argument
        """
        message=message+"\n"
        self.logwindow.insert_at_cursor(message,
                                     len(message))
        self.handlerdepth+=1
        if self.handlerdepth==1 and \
        gtk.events_pending():
            gtk.mainiteration()
        self.handlerdepth-=1
        return
将 GUI 从 GTK v1 移动到 GTK v2

pyGTK FAQ 中关于将您的应用程序从 GTK v1 移植到 GTK v2 的条目正变得越来越完整。但是,您应该意识到您将面临的一些问题。显然,您所有的 GtkText 小部件都需要替换为 Gtk.TextView 小部件。GUI 中的相应代码也必须更改以适应此移动。同样,您在 GTK v1 中完成的任何列表或树都必须重做。您可能会感到惊讶的是,您还需要重做所有对话框,以 GTK v2 格式重新制作它们,这看起来更漂亮。

此外,还发生了一些语法更改,例如 GDK 移动到 gtk.gdk,libglade 移动到 gtk.glade。在大多数情况下,这些都是简单的搜索和替换。例如,使用 GtkText.insert_defaults 而不是 GtkTextBuffer.insert_at_cursor(),使用 radiobutton.get_active() 而不是 radiobutton.active。您可以使用 libglade 发行版的 Python 脚本将您的 Glade v1 文件转换为 Glade v2 文件。这使您可以开始使用 GUI,但在移植代码之前,您可能需要加载 Glade v2 并进行一些重新配置。

最终说明
  • 不要忘记您可以从 Glade 小部件树中剪切和粘贴。这可以使重新设计快速而轻松。

  • 取消设置“属性”窗口中的任何可能的位置,以便您的启动看起来不会很奇怪。

  • 如果您有一个您认为其他人也可能有的问题,请将其添加到 pyGTK FAQ。

  • GNOME IRC 服务器有一个有用的 #pygtk 频道。如果没有频道上的人(尤其是 James Henstridge)的帮助,我不可能编写 CANVAS。开源社区的贡献在于,主要的开发人员通常可以回答新手问题。

完成的演示代码可从 ftp.linuxjournal.com/pub/lj/listings/issue113/6586.tgz 获取。

Dave Aitel 是 Immunity, Inc. 的创始人,该公司是一家位于纽约的安全咨询公司。CANVAS 是 Immunity 的渗透测试和漏洞利用开发框架,完全使用 Python 和 pyGTK 编写。有关 Immunity 的更多信息,请访问 www.immunitysec.com

加载 Disqus 评论