在 Python 世界中有一个非常有趣的 [anygui] 项目,它已经进入了早期的开发阶段。[anygui] 项目打算作为许多主要图形工具箱的下层 API。一旦完全开发成功,Python 程序员就可以调用公共 [anygui] 函数 — 例如,为创建一个窗口 — 可由“最适当好用”的工具箱来完成这项工作。在 Windows 上,可以使用到 Win32 API(或者 wxWindows);在 MacOS 上,可以本机调用;在 BeOS 上,使用 Bethon;在 Linux 上,使用 TKinter 或者 GTK;在 Telnet 屏幕上使用 ncurses — 所有这些都取决于给定的机器上安装的和可用的软件。本文讨论了 [anygui] 当前的开发状态,以及该项目要达到的目标。
编写一次,到处显示!
许多年前当 Java 刚出现时,它的一个重要承诺就是实现代码“编写一次,随处运行”的想法。起先,主要考虑将 Java 用户界面作为 applet,嵌入到 Web 浏览器中。一段时间后,独立的 AWT 应用程序成为更流行的概念。接下来,AWT 通常被 Swing 所取代。Swing 又变成了 Bean(构建在 Swing 上,但有另外的要求)。这样依次下来,Swing 类从 Java 版本中添加、删减,不断来回变化着。
有关 Java 的一个很流行的笑话是,“编写一次,到处调试”。至少可以确定的是,您不可能编写了一个 Java 应用程序后,非常自信地认为它可以运行在您应用程序的每个用户机器上 — 除非您愿意要求每个用户做相当多的工作来获取 Java 版本和配置,使它们完全符合您特定的应用程序。应用程序是否运行取决于 Java 版本,以及甚至特定的供应商和安装 Java 虚拟机(JVM)的平台。
就大多数方面而言,如 Python、Perl 和 Tcl 这样的脚本语言,要比 Java 具有更好的可移植性。例如,对于大多数 Python 脚本,程序员感到十分自信的是,发送到多个用户的脚本在每台目标机器上都将正确和完全一致地运行(可能有最低版本的要求 — 这要比 Java 上的要求简单得多、可靠得多)。当然,Java 除了不完美的移植性外,它也有许多优势:静态输入(许多人想要它)、庞大的类库、卓越的文档、细心的设计选择。但是有关那些语言的注意事项并不是我在这里感兴趣的话题。
Python 脚本可移植性中有一个地方比 Java 差很多,那就是在用户界面中。对于一个命令行工具,这一点不成问题。但当您希望复杂的用户交互时 — 特别专门针对图形界面时 — Python 实际上什么也不能提供。对于所有的小故障和小错误,Java 通常确实为每个带 JVM 的平台提供了 Swing 和 AWT 这些基本的 GUI。相反,Python 完全没有任何“标准”的 GUI 库。
许多人都表示他们期望有标准的 Python GUI。Tkinter 恰在这时到来 — 它有 Windows 和 UNIX/X Window 系统稳定的版本,并且有一个过得去的 MacOS 版本。但您还需要在系统上安装 TCL 和 TK,以及被忽略的“非主流”平台,如 BeOS 和 OS/2。各类倡导者建议,采用一些其它库/绑定将会是一个更好的选择(有许多库可以进行选择;请参阅参考资料)。而每个库/绑定支持一个期望平台的子集;并且最重要的是,没有一个库/绑定被一致地接受(因此,没有一个成为标准而随 Python 分发版一起发行)。我们无法编写带用户界面的应用程序,并且无法确保实际 用户可以与它进行相互操作。
重新考虑这个问题
Java 思想创建了每个 JVM 必须实现的能力的一个标准集合。Java GUI 是根据规定而存在。Python 式的方法可能来自一个不同的角度。不再命令每台机器遵从某个确定的 API,取而代之的是,只是确定给定机器可以做什么,然后从那里开始工作。API 仅作为底层平台所完成工作的包装器。
一旦以 Python 方式考虑问题时,anygui 做了您确实希望做的事情。从 anydbm 模块中取出它的名字和相关说明,在运行时找到“最适当可用”的数据库后端, anygui 在自身应用程序正在运行的系统上会找到最适当可用的 GUI 后端。anygui 强调提供一个可以和每个后端一起工作的实用的界面元素集合;特定的后端可能自己能够提供更高级的界面,但 anygui 提供的是它们的公共的界面。
写本文的时候,anygui 还是一个 alpha 级的项目。对一个目标后端子集,anygui 已经做得非常好。但由于目标是建立一个(接近于)通用的包装器,只有一个子集在工作显然是不够的。最终,如果 anygui 达到了它的目标,则在每个 Python 分发版中,anygui 都将作为标准的 Python 包包含在其中,那才是有意义的(就如同不管什么依赖于系统的后端,Python 分发版中都包括了 anydbm 或者 xml.sax)。毕竟,这一点才能确保每个用户都拥有了它。顺便提一下, anygui 是纯 Python;它本身不需要 C/C++ 或者其他较低级语言的任何东西(当然,如果有用,anygui 应该找一些支持的 GUI 库)。
平台和图片
为了写本文,我快速研究了大多数工作后端。还有几个后端没有实现,或者只实现了部分功能。已经实现的有 Tkinter、Java Swing、win32all、PyGTK 和 wxPython。BeOS 本机(带 Bethon)只实现了部分功能,但可能每天都构建一次进行新的改进。PyQT 和 MacOS 本机已经编制了规划,并且已经进行了开发,还没有创建这些包装器的实现;当然,随着时间的推移,这些都可能发生变化。关于直接的 xlib 后端,也一直有一些讨论,但目前没有人自愿负责这项工作。
上面所有的图形工具箱以一种非常相似的方式工作,或将以一种非常相似的方式工作。我承认我在大多数后端工具箱方面的知识很有限 — 但从我的理解,anygui API 在很大程度上与 Tkinter 相似。本质上,这个策略是创建带回调的一串窗口小部件,然后进入一个主事件循环。
将来还可能会有一些其它后端,它们会打破这个“标准”的 GUI 工具箱模型。在某些方面,它们看起来似乎最有趣,或者至少很新颖。一个已经规划好的后端被期望由我来负责 — 但是在开发最初的版本时,我有点懈怠。希望在您读本文时,这会有所改进。我自己开发的小型后端是 ncurses。如果它最终实现,则这开创了甚至在文本模式的终端(譬如,SSH/telnet 会话)或者只是在纯 UNIX 机器(不带 X Window 系统)上运行 anygui 应用程序的可能性。
按照 curses 后端的风格,anygui 的项目负责人 Magnus Lie Hetland 已经建议了纯面向行界面(似乎有点倒退),它可以使用 readlines 支持。在这个方案中,菜单将简化成提示符,接着是选项选择,再接着是反馈或者结果等等。假想中的 anygui.backends.textgui 只需要 STDIN 和 STDOUT 来工作,这对于程序来说,是令人感兴趣的,因为它所需要的最少。否则,该程序可能(一点未变动)运行在复杂的图形化、事件驱动和 WIMP 界面(窗口、图标、鼠标指针)。当然,到目前为止,它还只是一个想法。
还有一个古怪的想法也同样有趣。每人都有一个 Web 浏览器(几乎),即使那个浏览器碰巧是 lynx 或者是 links。Python 标准的 webbrowser 模块允许以一种与 anygui 以及和 anygui 具有相似功能的软件的方式,灵活地启动一个“最适当可用”的 Web 浏览器。如果那个浏览器与某类 LOCALHOST 服务器通信,那么所有您想要的基本界面设备都完全在 Web 浏览器内(按钮、输入域、文本区域、图形等等)。这个后端也处于规划阶段。
一幅图片抵得上用千言万语来表达的含义(至少有时候)。由于我的编辑们,出于善意,不希望将本专题出版为一万字的巨著,所以就让我们看几幅屏幕快照。为了说明需要,使用了一个小玩具应用程序,有一些按钮,并且启用了几个不活动性的按钮(它的源代码显示如下)。其中还包含了几个文本标签。其它窗口小部件示例包含在 anygui 分发版的 test 目录中。
首先值得一看的,我们可能认为是作为“默认缺省值”的后端 Tkinter。这个版本看上去和使用起来就与它应该做到的完全一致。但是, win.destroy() 调用就有点好笑 — 它没有立即破坏窗口(并且关闭应用程序),而是一旦当窗口受到足够的注意(比如移动窗口),它就将窗口变为一个会消失的鬼怪。如同我说的,我们还停留在 alpha 阶段。这个示例在 Win98 下运行:
Tkinter 下的按钮应用程序(在 Win98 上)
在 Windows 下运行时,还有一个利用 win32all 模块使用 Windows 本机调用的选项。来自 ActiveState 的 ActivePython 分发版在缺省情况下有这个选项;否则,还需要另外获得此模块(也是来自 ActiveState)。总之,这种绑定是我看到的运行最好的一种 — 但那也只是反映在我做测试的版本上。标签的放置与 Tkinter 上的有一点不同,这表示,一个人对不同后端不可能得到完全相同的视觉审美观。
Win32 下的按钮应用程序(在 Win98 上)
接下来,我决定将话题转到一个完全不同的平台上。在 OS/2 Warp 4 下运行 Python,我得到了如下的结果。由于某些原因,在文本标签前面多了一个不必要的 <html>。尽管这是一个很小的错误,但令人印象深刻的是,同一段代码运行在不同的平台上,结果竟然是如此的不同:
Java Swing 下的按钮应用程序(在 OS/2 Warp 4 上)
然后转到 Linux 平台,在已经安装了 PyGTK 的系统上运行相同一个应用程序。只为了好玩,我在几个不同的 window 管理器下运行该应用程序。首先在 Enlightenment 上:
GTK 下的按钮应用程序(在 Enlightenment 上)
然后在 WindowMaker 上:
GTK 下的按钮应用程序(WindowMaker 上)
在不同窗口管理器下的窗口框架内所有东西都是相同的。标签的对齐和尺寸与 Windows 下的有点不同(需要额外增加几个像素,避免截断字)。我无法很容易地在我任何一个系统上安装 wxPython,但我想结果应该是相似的。
BeOS 后端还处于一种较原始的状态。甚至玩具应用程序也失败了。但是,基本的 Window 类工作得很好:
BeOS r5 上的窗口测试应用程序
一些自身相同的代码
这段运行在所有证明过的平台上的代码非常简单。在我的示例中,该程序的上半部分完全是实现一个 switch,允许从命令行手工选择要使用的后端。在产品代码中,您将不会希望有这样的选择;但对于早期的测试,就象我所做的这些,是很有帮助。然而请注意,这里显示的屏幕快照的这些所有测试完全在没有任何命令行选项的情况下运行 — 后端的选择是自动的。让我们来看一下代码:
[anygui] 的‘button.py’文本应用程序
import sys
if len(sys.argv)==1or sys.argv[1].upper()=='DEFAULT':
from anygui import Window, Button, Application, Label
elif sys.argv[1].upper()=='TK':
from anygui.backends.tkgui import Window, Button, Application, Label
elif sys.argv[1].upper()=='MSW':
from anygui.backends.tkgui import Window, Button, Application, Label
elif sys.argv[1].upper()=='BEOS':
from anygui.backends.beosgui import Window, Button, Application, Label
elif sys.argv[1].upper()=='GTK':
from anygui.backends.gtkgui import Window, Button, Application, Label
elif sys.argv[1].upper()=='JAVA':
from anygui.backends.javagui import Window, Button, Application, Label
elif sys.argv[1].upper()=='WX':
from anygui.backends.wxgui import Window, Button, Application, Label
def say_hello():
global bye
print"Hello, world!"
bye._set_enabled(1)
app = Application()
win = Window(width=150, height=150, title="Beatles Lyric")
win.add(Label(x=10, y=10, width=140, text = "I don't know why you say..."))
bye = Button(x=30, y=40, width=70, height=30, text="Goodbye",
action=lambda: win.destroy(), enabled=0)
win.add(bye)
win.add(Label(x=10, y=70, width=120, height=32, text = "When I say..."))
hi = Button(x=30, y=100, width=70, height=30, text="Hello", action=say_hello)
win.add(hi)
win.show()
app.run()
应用程序的主框架只由四个步骤组成:(1)创建一个应用程序;(2)创建一个或多个窗口;(3)在窗口中添加一些窗口小部件;(4)调用 app.run() 事件循环。窗口小部件选项都作为命名的参数传递。
结束语
在目前的 anygui alpha 版中已经有编写基本“获取一些数据,对它进行处理,并且显示一些结果”的应用程序所需要的一切东西。讨论列表中包含了许多关于更多细微差别的事件处理、视图模型等有趣的主题。此外, anygui API 还需要正式的地编制文档。然而在承诺方面,在很长一段时间内 anygui 与我看到的任何 Python 库相比,更令我兴奋。几乎很难想象在 Python 自身运行的地方,它能够透明地获取复杂的用户界面,这是多么的方便 — 根本不需要为平台细节方面更改一行代码。
参考资料
* 可以从 SourceForge 页面开始探索 anygui。从那里,可以浏览到开发人员邮件列表、文档、最新版本的下载等等。
* Cameron Laird 已经编写了 Python 详尽列表的 GUI 绑定。Laird 的列表包含了有关每个绑定的状态以及到相关项目页的链接的注释摘要。
* 请查阅 Damond Walker 的 tinter 和 Laurent Pelecq 的 增强的 ctinter。curses 本身是一个标准 Python 模块(它需要实际的 ncurses 库来工作)。
* 在 developerWorks 上,David Mertz 编写的与“可爱的 Python”专栏相关的文章还有 "Charming Python: TK programming in Python" 和 "Charming Python: Curses programming"。
* 请浏览 developerWorks 上的更多 Linux 参考资料。
* 请浏览 developerWorks 上的 更多开放源码参考资料。
关于作者
David Mertz 一直致力于对各类知识进行融会贯通;唯一使他比看到一个好的交互式多媒体库更高兴的事是回到打卡和批处理系统的想法。可以通过 mertz@gnosis.cx 与 David 联系;http://gnosis.cx/publish/ 上详细地介绍了他的生活。非常欢迎对以前的、本篇和以后的专栏文章提出建议和意见。