第5章 GUI编程

GUI的东西应该会很难。它甚至可以塑造性格。

——Jim Ahlstrom,1995年5月

(口述于Python Workshop)

本章内容:

简介;

Tkinter和Python编程;

Tkinter示例;

其他GUI简介;

相关模块和其他GUI。

本章将对图形用户界面(Graphical User Interface,GUI)编程进行简要的介绍。无论你刚接解本领域,还是希望学到更多相关知识,亦或是想要看到Python中是如何实现的,本章都会正合你意。这短短的一章不可能展示所有GUI应用开发的东西,但是会为你奠定一个坚实的基础。我们将主要使用的GUI工具包是Python默认的GUI库Tk,通过Python的接口Tkinter (“Tk interface”的缩写)可以访问Tk。

Tk 并不是最新和最好的,也没有包含最强大的 GUI 构建模块集,但是它足够易用,你可以使用它构建能够运行在大多数平台下的GUI。我们将给出几个使用Tkinter的简单和中等难度的例子,然后是几个使用其他工具包的例子。当你完成本章的学习后,你将有能力构建更为复杂的应用,并可以转而使用更加现代化的工具包。对于当前大多数主流的工具包(包括商业系统),Python都有其对应的绑定或适配器。

5.1 简介

在开始GUI编程之前,首先介绍Python默认的UI工具包Tkinter。我们将先从安装Tkinter开始,因为Tkinter不总是默认安装的(尤其是当你自己从源码构建Python的时候)。接下来会是一个对客户端/服务端架构的快速回顾,该话题在第2章中已经进行过介绍,不过在这里还会有一些关联。

5.1.1 Tcl、Tk和Tkinter

Tkinter是Python的默认GUI库。它基于Tk工具包,该工具包最初是为工具命令语言(Tool Command Language,Tcl)设计的。Tk普及后,被移植到很多其他的脚本语言中,包括Perl (Perl/Tk)、Ruby(Ruby/Tk)和Python(Tkinter)。结合Tk的GUI开发的可移植性与灵活性,以及与系统语言功能集成的脚本语言的简洁性,可以让你快速开发和实现很多与商业软件品质相当的GUI应用。

如果你是GUI编程新手,会惊喜地发现它多么简单。此外,你还会发现使用Python 和Tkinter可以提供给你一种高效而又令人兴奋的方法来创建有趣(可能还很有用)的应用,而如果直接使用C/C++的原生窗体系统库进行编程则会花费较长的时间。一旦设计好了应用程序及其外观,就可以使用称为控件(widget)的基础构建块来拼凑出你想要的东西,最后再添加功能使其真实可用。

如果你是使用Tk的老手,无论熟悉的是Tcl还是Perl,都会发现Python给予了GUI编程一种新的方式。最重要的是,它提供了一种更快速地构建GUI程序的原型系统。此外还要记住,你依然能够使用 Python 的系统访问、网络操作、XML、数值与可视化处理、数据库访问、所有其他标准库和第三方扩展模块。

只要系统中安装了Tkinter,不超过15分钟时间就可以让你的第一个GUI程序运行起来。

5.1.2 安装和使用Tkinter

Tkinter在系统中不是默认必须安装的,可以通过在Python解释器中尝试导入Tkinter模块(Python 1和2版本,在Python 3中重命名为tkinter)来检查Tkinter是否可用。如果Tkinter可用,则不会有错误发生,如下所示。

>>> import Tkinter

>>>

如果你的Python解释器在编译时没有启用Tkinter,模块导入将会失败。

>>> import Tkinter

Traceback (innermost last):

File "<stdin>", line 1, in ?

File "/usr/lib/pythonX.Y/lib-tk/Tkinter.py", line 8, in ?

import _tkinter # If this fails your Python may not

be configured for Tk

ImportError: No module named _tkinter

你可能需要重新编译Python解释器以使用Tkinter。这通常会涉及编辑Modules/Setup文件,然后启用所有正确的设置,来编译带有Tkinter的Python解释器;或者勾选安装Tk到你的系统中。查阅README文件,以获取你的Python版本编译Tkinter到系统中的特定说明。编译好解释器后,需要启动一个新的Python解释器,否则,它还会和没有Tkinter(实际上,它就是旧的解释器)的表现一样。

5.1.3 客户端/服务端架构

第2章介绍了客户端/服务端计算的概念。窗口系统是软件服务器的另一个例子,它们运行在一个带有显示设备(如显示器)的计算机中。同样地,还有需要客户端——需要在端窗口环境中执行的程序,也称为GUI应用。上述这些应用无法脱离窗口系统独立运行。

当引入网络编程后,该架构变得更加有趣。通常一个GUI应用执行时,它会在启动程序的计算机上进行显示(通过窗口服务器);但是在一些网络窗口环境中,也可以选择另一台计算机的窗口服务器进行应用的显示,例如UNIX中的X Window系统。因此,可以在一台计算机上运行GUI程序,而在另一台机器上进行显示。

5.2 Tkinter和Python编程

本节首先会介绍通用的GUI编程,然后会重点关注如何使用Tkinter及其组件创建Python的GUI程序。

5.2.1 Tkinter模块:添加Tk到应用中

那么为了让Tkinter成为应用的一部分,你需要做些什么呢?首先,已经存在的应用并不是必需的。如果你愿意,可以创建一个纯GUI程序,不过没有让人感兴趣的底层功能的程序不会有什么用处。

让GUI程序启动和运行起来需要以下5个主要步骤。

1.导入Tkinter模块(或from Tkinter import *)。

2.创建一个顶层窗口对象,用于容纳整个GUI应用。

3.在顶层窗口对象之上(或者“其中”)构建所有的GUI组件(及其功能)。

4.通过底层的应用代码将这些GUI组件连接起来。

5.进入主事件循环。

第一步是琐碎的:所有使用Tkinter的GUI程序都必须导入Tkinter模块。获得Tkinter的访问权是首要步骤(参见5.1.2节)。

5.2.2 GUI编程介绍

在举例之前,先简单介绍GUI应用开发。这将为你今后的学习提供一些通用的背景知识。

创建一个GUI应用就像艺术家作画一样。传统上,艺术家使用单一的画布开展创作。其工作方式如下:首先会从一块干净的石板开始,这相当于用来构建其余组件的顶层窗口对象。可以将其想象为房屋的地基或艺术家的画架。换句话说,必须在浇灌好混凝土或搭建起画架之后,才能把真实的结构或画布拼装在上面。在Tkinter中,这个基础称为顶层窗口对象。

窗口和控件

在 GUI编程中,顶层的根窗口对象包含组成 GUI应用的所有小窗口对象。它们可能是文字标签、按钮、列表框等。这些独立的GUI组件称为控件。所以当我们说创建一个顶层窗口时,只是表示需要一个地方来摆放所有的控件。在Python中,一般会写成如下语句。

top = Tkinter.Tk() # or just Tk() with "from Tkinter import *"

Tkinter.Tk()返回的对象通常称为根窗口,这也是一些应用使用root而不是top来指代它的原因。顶层窗口是那些在应用中独立显示的部分。GUI程序中可以有多个顶层窗口,但是其中只能有一个是根窗口。可以选择先把控件全部设计好,再添加功能;也可以边设计控件边添加功能(这意味着上述步骤中的第3步和第4步会混合起来做)。

控件可以独立存在,也可以作为容器存在。如果一个控件包含其他控件,就可以将其认为是那些控件的父控件。相应地,如果一个控件被其他控件包含,则将其认为是那个控件的子控件,而父控件就是下一个直接包围它的容器控件。

通常,控件有一些相关的行为,比如按下按钮、将文本写入文本框等。这些用户行为称为事件,而GUI对这类事件的响应称为回调。

事件驱动处理

事件可以包括按钮按下(及释放)、鼠标移动、敲击回车键等。一个GUI应用从开始到结束就是通过整套事件体系来驱动的。这种方式称为事件驱动处理。

最简单的鼠标移动就是一个带有回调的事件的例子。假设鼠标指针正停在 GUI 应用顶层窗口的某处。如果你将鼠标移动到应用的另一部分,鼠标移动的行为会被复制到屏幕的光标上,于是看起来像是根据你的手移动的。系统必须处理的这些鼠标移动事件可以绘制窗口上的指针移动。当释放鼠标时,不再有事件需要处理,此时屏幕会重新恢复闲置的状态。

事件驱动的GUI处理本质上非常适合于客户端/服务端架构。当启动一个GUI应用时,需要一些启动步骤来准备核心部分的执行,就像网络服务器启动时必须先分配套接字并将其绑定到本地地址上一样。GUI应用必须先创建所有的GUI组件,然后将它们绘制在屏幕上。这是布局管理器(geometry manager)的职责所在(稍后会详细介绍)。当布局管理器排列好所有控件(包括顶层窗口)后,GUI应用进入其类似服务器的无限循环。这个循环会一直运行,直到出现GUI事件,进行处理,然后再等待更多的事件去处理。

布局管理器

Tk 有 3 种布局管理器来帮助控件集进行定位。最原始的一种称为 Placer。它的做法非常直接:你提供控件的大小和摆放位置,然后管理器就会将其摆放好。问题是你必须对所有控件进行这些操作,这样就会加重编程开发者的负担,因为这些操作本应该是自动完成的。

第二种布局管理器会是你主要使用的,它叫做Packer,这个命名十分恰当,因为它会把控件填充到正确的位置(即指定的父控件中),然后对于之后的每个控件,会去寻找剩余的空间进行填充。这个处理很像是旅行时往行李箱中填充行李的过程。

第三种布局管理器是Grid。你可以基于网格坐标,使用Grid来指定GUI控件的放置。Grid会在它们的网格位置上渲染GUI应用中的每个对象。本章将使用Packer。

一旦Packer确定好所有控件的大小和对齐方式,它就会在屏幕上将其放置妥当。

当所有控件摆放好后,可以让应用进入前述的无限主循环中。在Tkinter中,代码如下所示。

Tkinter.mainloop()

一般这是程序运行的最后一段代码。当进入主循环后,GUI就从这里开始接管程序的执行。所有其他行为都会通过回调来处理,甚至包括退出应用。当选择File菜单并单击Exit菜单选项,或者直接关闭窗口时,就会调用一个回调函数来结束这个GUI应用。

5.2.3 顶层窗口:Tkinter.Tk()

之前提到过所有主要控件都是构建在顶层窗口对象之上的。该对象在Tkinter中使用Tk类进行创建,然后进行如下实例化:

>>> import Tkinter

>>> top = Tkinter.Tk()

在这个窗口中,可以放置独立的控件,也可以将多个组件拼凑在一起来构成GUI程序。那么有哪些种类的控件呢?现在就介绍这些Tk控件。

5.2.4 Tk控件

在本书写作时,总共有 18 种 Tk 控件,表 5-1 所示为这些控件的描述。最新的控件有LabelFrame、PanedWindow和Spinbox,这些都是从Python 2.3版本开始增加的(通过Tk 8.4)。

表5-1 Tk控件 第5章 GUI编程 - 图1

我们将不会对 Tk 控件进行详细介绍,因为已经有很多不错的文档可以供你参阅了,比如Python主站上的Tkinter主题页,或大量印刷资源和网上关于Tcl/Tk的资源(可以参见附录B)。不过,后面会给出几个简单的例子来帮助你起步。

核心提示:默认参数是你的朋友

GUI开发利用了Python的默认参数,因为Tkinter的控件中有很多默认行为。除非你非常清楚自己所使用的每个控件的每个可用选项的用法,否则最好还是只关心你要设置的那些参数,而让系统去处理剩下的参数。这些默认值都是精心选择出来的。即使没有提供这些值,也不用担心应用程序在屏幕上的显示会有什么问题。作为一条基本规则,程序是由一系列优化后的默认参数创建的,只有当你知道如何精确定制你的控件时,才应该使用非默认值。

5.3 Tkinter示例

现在来看下我们的第一组GUI脚本,其中的每个脚本都会介绍一个控件,并可能会展示一种使用控件的不同方式。几个非常基础的例子后,是一个中等难度的示例,该示例会与GUI编程实践有更多的关联性。

5.3.1 Label控件

在示例5-1的tkhello1.py中,给出了Tkinter版本的“Hello World!”。特别 是,它会展示Tkinter应用如何启动,并着重强调了Label控件。

示例5-1 Label控件演示(tkhello1.py)

我们的第一个Tkinter示例,除了“Hello Word!”,还能是什么呢?特别是,我们会介绍第一个控件:Label。

第5章 GUI编程 - 图2

在第1行中,创建了一个顶层窗口。接下来是Label控件,它包含了那串久负盛名的字符串。然后让Packer来管理和显示控件,最后调用mainloop()运行这个GUI应用。图5-1所示为运行该GUI应用后的结果。

第5章 GUI编程 - 图3 图5-1 Tkinter的Label控件

5.3.2 Button控件

下一个例子(tkhello2.py)与第一个例子很相似。不过,这里创建的控件是按钮,而不再是标签。示例5-2为其源代码。

示例5-2 Button控件演示(tkhello2.py)

这个例子和tkhello1.py非常相似,除了这里创建的是Button控件而不是Label控件外。

第5章 GUI编程 - 图4

一开始的几行完全相同,只有在创建Button控件时有所区别。该按钮有一个额外的参数:Tkinter.quit()方法。该参数会给按钮安装一个回调函数,当按钮被按下(并且释放)后,整个程序就会退出。最后两行是通用的 pack()方法和 mainloop()调用。这个简单的按钮应用如图5-2所示。

第5章 GUI编程 - 图5 图5-2 Tkinter的Button控件

5.3.3 Label和Button控件

在示例5-3中,会把tkhello1.py和tkhello2.py结合到一起,组成既包含标签又包含按钮的tkhello3.py脚本。此外,它还会使用更多的参数,而不只是满足于自动生成的默认值。

除了控件的额外参数之外,还可以看到Packer的一些参数。fill参数告诉Packer让QUIT按钮占据剩余的水平空间,而expand参数则会引导它填充整个水平可视空间,将按钮拉伸到左右窗口边缘。

示例5-3 Label和Button控件演示(tkhello3.py)

本示例使用了Label和Button控件。相比于在创建控件时使用默认参数,这里指定了几个额外的参数,用于学习Button控件更多的知识及其配置方法。

第5章 GUI编程 - 图6

如图5-3所示,在Packer没有收到其他指示时,所有控件都是垂直排列的(自上而下依次排列)。如果想要水平布局则需要创建一个新的 Frame 对象来添加按钮。该框架将作为单个子对象来代替父对象的位置(参见5.3.6节示例5-6中listdir.py模块的按钮)。

第5章 GUI编程 - 图7 图5-3 Tkinter的Label和Button控件

5.3.4 Label、Button和Scale控件

最后一个简单例子tkhello4.py会额外调用Scale控件。这里Scale用于与Label控件进行交互。Scale滑块是用来控制Label控件中文字字体大小的工具。滑块的位置值越大,字体越大;反之亦然。示例5-4为tkhello4.py的代码。

本脚本的新功能包括一个resize()回调函数(第5~7行),该函数会依附于Scale控件。当Scale控件的滑块移动时,这个函数就会被激活,用来调整Label控件中的文本大小。

此外,还定义了顶层窗口的大小为250150(第10行)。本脚本与之前3个脚本的最后一个不同之处是导入Tkinter模块的属性到命名空间时使用的是from Tkinter import 。尽管因为会污染命名空间而不推荐这种做法,但是这里依然如此使用的主要原因是这个应用会涉及对Tkinter属性的大量引用。直接导入Tkinter模块会造成访问每个属性时都需要使用其完整写法。而使用这种不推荐的简写方式虽然付出了一定代价,但是可以减少输入,并使得代码更加易读。

示例5-4 Label、Button和Scale控件演示(tkhello4.py)

最后要介绍的控件是Scale,此外还会重点了解控件是如何通过回调函数(如resize())与其他控件进行通信的。Label控件中的文本会受到Scale控件上操作的影响。

第5章 GUI编程 - 图8

如图5-4所示,滑块机制和当前的设定值都在窗口的主要部分中显示出来。同样,还可以从图5-4中看出,当用户移动滑动条/滑块到值36时GUI的状态。请注意,应用启动时滑块的初始值设定为12(第18行)。

第5章 GUI编程 - 图9 图5-4 Tkinter的Label、Button和Scale控件

5.3.5 偏函数应用示例

在看一个更复杂的GUI应用之前,让我们先回顾一下Core Python Programming或Core Python Language Fundamentals书中介绍的偏函数应用(P FA)。

偏函数在Python 2.5版本中添加进来,是函数式编程一系列重要改进中的一部分。使用偏函数,可以通过有效地“冻结”那些预先确定的参数来缓存函数参数,然后在运行时,当获得需要的剩余参数后,可以将它们解冻,传递到最终的参数中,从而使用最终确定的所有参数去调用函数。

偏函数最好的一点是它不只局限于函数。偏函数可以用于可调用对象(任何包括函数接口的对象),只需要通过使用圆括号即可,包括类、方法或可调用实例。对于有很多可调用对象,并且许多调用都反复使用相同参数的情况,使用偏函数会非常合适。

GUI编程是一个很好的偏函数用例,因为你很有可能需要GUI控件在外观上具有某种一致性,而这种一致性来自于使用相同参数创建相似的对象时。我们现在要实现一个应用,在这个应用中有很多按钮拥有相同的前景色和背景色。对于这种只有细微差别的按钮,每次都使用相同的参数创建相同的实例简直是一种浪费:前景色和背景色都是相同的,只有文本有一点不同。

本例中将使用交通路标来进行演示,在该应用中我们会尝试创建文字版本的路标,并将其根据标志类型进行区分,比如严重、警告、通知等(就像日志级别那样)。标志类型决定了创建时的颜色方案。例如,严重级别标志是白底红字,警告级别标志是黄底黑字,通知(即标准级别)标志是白底黑字。在这里,“Do Not Enter”和“Wrong Way”标志属于严重级别,“Merging Traffic”和“Railroad Crossing”属于警告级别,而“Speed Limit”和“One Way”属于标准级别。

示例5-5中的应用会创建这些标志,当然它们只是按钮。当用户按下按钮时,会弹出相应的Tk对话框:严重/错误、警告或通知。虽然这不够令人兴奋,但仍然能够说清这些按钮是如何创建的。

示例5-5 路标偏函数 GUI应用(pfaGUI2.py)

根据标志类型创建拥有合适前景色和背景色的路标。使用偏函数可以帮助你“模板化”通用的GUI参数。

第5章 GUI编程 - 图10

第5章 GUI编程 - 图11

当你执行这个应用时,可以看到如图5-5所示的GUI输出。

第5章 GUI编程 - 图12 图5-5 Mac OS X的XDarwin下的路标偏函数GUI应用

逐行解释

第1~18行

先在应用中导入了functools.partial()、几个 Tkinter属性以及几个Tk对话框(第1~5行)。之后,根据类别定义了一些标志(第7~18行)。

第20~28行

Tk对话框用做按钮的回调函数,将在创建每个按钮时使用它们(第20~23行)。之后启动Tk,设置标题,并创建一个QUIT按钮(第25~28行)。

第30~33行

这些行展示了偏函数的魔法。我们使用了两阶偏函数。第一阶模板化了Button类和根窗口top。这意味着每次调用MyButton时,它就会调用Button类(Tkinter.Button()会创建一个按钮),并将top作为它的第一个参数。我们将其冻结为MyButton。

第二阶偏函数会使用我们的第一阶偏函数,并对其进行模板化。我们会为每种标志类型创建单独的按钮类型。当用户创建一个严重类型的按钮 CritButton 时(比如通过调用CritButton()),它就会调用包含适当的按钮回调函数、前景色和背景色的 MyButton,或者说使用top、回调函数和颜色这几个参数去调用Button。你可以看到它是如何一步步展开并最终调用到最底层的,如果没有偏函数这个功能,这些调用本来应该是由你自己执行的。WarnButton和ReguButton也会执行同样的操作。

第35~42行

设置好按钮后,我们会根据标志列表将其创建出来。我们将使用一个Python可求值字符串,该字符串由正确的按钮名、传给按钮标签的文本参数以及 pack()操作组成。如果这是一个严重级别的标志,我们会把所有字符大写;否则,按照标题格式进行输出。第39行代码会用到Python 2.5版本开始引入的三元/条件操作符。每个按钮会通过eval()函数进行实例化,结果如图5-5所示。最后,我们进入主事件循环来启动GUI程序。

如果你使用的是2.4或更老的版本,可以使用“and/or”语法比较轻松地替代三元操作符,但是functools.partial()就比较难移植过去了,所以还是推荐使用2.5或更新的版本来执行这个示例应用。

5.3.6 中级Tkinter示例

我们将使用一个更复杂的脚本来结束本节,即示例 5-6 中的 listdir.py。这个应用是一个目录树遍历工具。它会从当前目录开始,提供一个文件列表。双击列表中任意其他目录,就会使得工具切换到新目录中,用新目录中的文件列表代替旧文件列表。

示例5-6 文件系统遍历GUI(listdir.py)

这个稍高级的GUI程序扩展了控件的使用,新增了列表框、文本框和滚动条。此外,还增加了鼠标单击、键盘按下、滚动操作等回调函数。

第5章 GUI编程 - 图13

第5章 GUI编程 - 图14

第5章 GUI编程 - 图15

在图5-6中,我们可以看到在Windows系统中这个GUI程序的样子。而该应用在POSIX系统上的截图如图5-7所示。

第5章 GUI编程 - 图16 图5-6 Windows下的目录列表GUI应用

逐行解释

第1~5行

最开始的这几行包括UNIX启动行,以及对os模块、time.sleep()函数和Tkinter模块所有属性的导入。

第9~13行

这几行定义了DirList类的构造函数和一个代应用的对象。然后创建了第一个Label控件,其中的文本是应用的主标题和版本号。

第15~19行

这里声明了 Tk 的一个变量 cwd,用于保存当前所在的目录名——之后我们会看到它是如何派上用场的。然后又创建了另一个Label控件,用于显示当前的目录名。

第21~29行

这一部分定义了本GUI应用的核心部分Listbox控件dirs,该控件包含了要列出的目录的文件列表。Scrollbar可以让用户在文件数超过Listbox的大小时能够移动列表。上述这两个控件都包含在Frame控件中。通过使用Listbox的bind()方法,Listbox的列表项可以与回调函数(setDirAndGo)连接起来。

绑定意味着将一个回调函数与按键、鼠标操作或一些其他事件连接起来,当用户发起这类事件时,回调函数就会执行。当双击Listbox中的任意条目时,就会调用setDirAndGo()函数。而Scrollbar通过调用Scrollbar.config()方法与Listbox连接起来。

第5章 GUI编程 - 图17 图5-7 UNIX下的目录列表GUI应用

第31~34行

然后创建了一个文本框,用户可以在其中输入想要遍历的目录名,从而可以在 Listbox中看到该目录中的文件列表。这里给这个文本框添加了一个回车键的绑定,这样用户除了可以单击按钮外,还可以敲击回车键来更新文件列表。我们之前在 Listbox 中看到过的鼠标绑定也是同样的应用。当用户双击 Listbox 中的条目时,与在文本框中手动输入目录名然后单击Go按钮有同样的效果。

第36~53行

接下来,定义了一个按钮的框架(bfm),用来放置3个按钮:一个“clear”按钮(clr)、一个“go”按钮(ls)和一个“quit”按钮(quit)。每个按钮在按下时都有其自己的配置和回调函数。

第55~57行

构造函数的最后一部分初始化GUI程序,并以当前工作目录作为起始点。

第59~60行

clrDir()方法会清空Tk字符串变量cwd(包含当前活动目录)。该变量会跟踪我们当前所处的目录,更重要的是,当发生错误时可以帮助我们回到之前的目录。此外,你还会注意到回调函数中变量ev的默认值是None。任何像这样的值都是由窗口系统传入的。它们在你的回调函数中可能会用到,也可能用不到。

第62~69行

setDirAndGo()方法设置要遍历的目录,并通过调用doLS()实现遍历目录的行为。

第71~108行

到目前为止,doLS()是整个GUI应用的最关键部分。它会进行所有安全检查(比如,目标是否是一个目录?它是否存在?)。如果发生错误,之前的目录就会重设为当前目录。如果一切正常,就会调用os.listdir()获取实际文件列表并在Listbox中进行替换。当后台忙于拉取新目录中的信息时,突出显示的蓝色条就会变成红色,直到新目录设置完毕后,它又会变回蓝色。

第110~115行

listdir.py的最后一段代码是这段代码的最主要部分。只有当直接调用脚本时,main()函数才会执行。当main()函数运行时,会创建GUI应用,然后调用mainloop()来启动GUI程序,之后由其控制应用的执行。

把这个应用的其他部分作为练习留给读者,推荐读者把整个应用看成一系列控件和函数的组合,这样可以更易于理解。如果你清晰地了解了每一部分,那么整个脚本也就不再那么令人畏惧了。

我们希望已经详细介绍了使用Python和Tkinter进行GUI编程的方法。请记住,熟悉Tkinter编程的最好方法是实践以及模仿示例!Python的发行包中有很多可以供你学习的演示应用。

如果你下载的是源码包,可以在Lib/lib-tk、Lib/idlelib和Demo/tkinter中找到Tkinter的演示代码。如果你把Win32版本的Python安装在C:\Python2x上,可以在Lib/lig-tk和Lib\idlelib中获取演示代码。后面的那个目录包含了最重要的Tkinter示例应用:IDLE IDE本身。此外,还有很多有关Tk编程的书籍可以作为进一步的参考,其中还有一本是专门写Tkinter的。

5.4 其他GUI简介

我们希望最终使用一个独立章节来讲解通用GUI开发,因为Python 有着极为丰富的图形工具包,不过,这是以后的事了。作为代替,我们将使用4个流行的工具包实现同一个简单的GUI应用,这4个工具包分别是:Tix(Tk接口扩展)、Pmw(Python MegaWidgets Tkinter扩展)、wxPython(wxWidgets的Python版本)以及PyGTK(GTK+的Python版本)。最后一个例子将演示如何在Python 2和Python 3中使用Tile/Ttk。你可以在5.5节找到获取更多信息的链接以及下载这些工具的地址。

Tix模块已经包含在Python标准库中了。而其他几个则是第三方模块,需要你自行下载。因为Pmw只是Tkinter的一个扩展,所以它是最容易安装的(只需要解压到你的site packages目录下)。WxPython与PyGTK涉及多个文件的下载和编译(除非选择Win32版本,有二进制安装包可以下载)。一旦工具包安装并得到验证,就可以开始了。后边的例子中不会局限于本章中已经见过的那几种控件,而是会介绍更多复杂的控件。

除了Label和Button外,这里还会介绍Control(即SpinButton)和ComboBox。Control控件由一个文本控件和一组靠近的箭头按钮组成,文本控件中的值可以被附近的一组箭头按钮“控制”或“上下调整”。ComboBox控件通常是由一个文本控件和一个下拉选项菜单组成的,列表中当前选定的条目会显示在文本控件中。

该应用相当基础:成对的动物要移走,而动物的总数在2~12个之间。Control控件用于跟踪动物总数,而ComboBox控件则包含了可以选择的动物种类。图5-8所示为这个GUI应用启动后每种工具包的显示情况。需要记住,默认情况下动物的数量为2个,且没有动物种类被选中。

当开始执行这个应用后,就会有些不同发生,图5-9所示为使用Tix工具包时修改了几个元素后的样子。

第5章 GUI编程 - 图18 图5-8 Win32下使用不同GUI工具包时的应用

第5章 GUI编程 - 图19 图5-9 应用在Tix工具包下修改后的版本

你可以在示例5-7~示例5-10中看到这个应用在4种GUI工具包下的代码。而在示例5-11中,我们将使用Tile/Ttk(同时包含Python 2和Python 3版本的代码)代替前面这4个例子。你会发现这些例子虽然都很相似,但每个都有其自己特殊的实现方式。此外,我们还将使用.pyw扩展名来阻止DOS命令行或终端窗口弹出。

5.4.1 Tk接口扩展(Tix)

我们从示例5-7的Tix模块开始。Tix是Tcl/Tk的一个扩展库,它添加了许多新的控件、图像类型以及其他可以使Tk作为一个GUI开发工具包的命令。让我们先来看下Python中是如何使用Tix的。

示例5-7 Tix GUI演示(animalTix.pyw)

这里的第一个例子使用了Tix模块,该模块是Python自带的。

第5章 GUI编程 - 图20

逐行解释

第1~7行

这些行是启动行、模块导入以及基本的 GUI操作。第 7行会断言应用中 Tix模块是可用的。

第8~27行

这些行创建了所有的控件:Label(第 9~11 行)、Control(第 13~16 行)、ComboBox (第18~21行)以及退出Button(第23~25行)。这些控件的构造函数和参数都是相当不言自明的,不需要详细解释。最后,我们在第27行进入了GUI的主事件循环。

5.4.2 Python MegaWidgets(PMW)

下面我们来看下Python MegaWidgets(如示例5-8所示)。这个模块用来解决Tkinter的老旧问题。它通过在GUI工具包中添加一些更新式的控件来延长Tkinter的生命力。

示例5-8 Pmw GUI演示(animalPmw.pyw)

第二个例子使用了Python的MegaWidgets包。

第5章 GUI编程 - 图21

Pmw的例子和Tix的例子十分相似,所以我们把逐行分析留给读者来进行。差异最大的代码行是Control控件的构造函数:Pmw的Counter。它提供了验证输入数据的方法。不同于用控件构造函数的关键字参数来指定最小值和最大值,Pmw 使用了一个“validator”来确保该值不会落在预期范围之外。

Tix和Pmw都是Tk与Tkinter的扩展,不过现在我们要离开Tk领域,去看一些完全不同的工具包:wxWidgets和GTK+。你会发现,当我们使用更加现代和健壮的GUI工具包以面向对象的方式开始编程时,代码行数会有所增加。

5.4.3 wxWidgets和wxPython

wxWidgets(以前称为 wxWindows)是一个可以构建图形用户应用的跨平台工具包。wxWidgets 使用 C++实现,并且由于它定义了一致、通用的API,因此可以在很多平台上使用。wxWidgets最大的优点是它使用了每个平台上 的原生GUI,所以你的程序可以和其他桌面应用有相同的视觉效果。另一个特点是你不会被局限于使用C++开发wxWidgets应用,因为它还有Python和Perl的接口。示例5-9所示为使用wxPython的动物应用。

示例5-9 wxPython GUI演示(animalWx.pyw)

第三个例子使用了wxPython(和wxWidgets)。请注意,这里将所有的控件都放在了一个“sizer”中来组织。此外,还需要注意本应用中更多面向对象的本质。

第5章 GUI编程 - 图22

第5章 GUI编程 - 图23

逐行解释

第5~37行

这里实例化了一个Frame类(第5~8行),其中只包括它的构造函数。该方法的唯一目标就是创建这些控件。在框架里,创建了一个 Panel。在这个面板中,使用 BoxSizer包含所有控件并对它们进行布局(第10~36行),这些控件包括Label(第12~14行)、SpinCtrl(第16~20行)、ComboBox(第22~27行)和退出Button(第29~34行)。

我们必须要手动把Label添加到SpinCtrl和ComboBox控件中,因为这些控件并不会包含有标签。当这些控件都创建好之后,把它们添加到sizer中,再把sizer设置到面板中,然后对所有控件进行布局。在第10行,你会注意到sizer是垂直布局的,这意味着控件会自上而下排列。

SpinCtrl 控件的一个缺点是它不支持“步进”函数。在其他 3 个例子中,可以单击箭头按钮使其每次增加或减少两个单位,但是对于这个控件不可以。

第39~51行

应用类实例化了之前设计的Frame对象,将其在屏幕上进行渲染,并设置为应用的最上层窗口。最后,启动行实例化GUI应用,并开始运行。

5.4.4 GTK+和PyGTK

最后是PyGTK版本,这个例子和wxPython GUI(见示例5-10)非常相似。最大的不同是只使用了一个类,在这里设置对象的前景色和背景色的代码十分冗长,尤其是按钮。

示例5-10 PyGTK GUI演示(animalGtk.pyw)

最后一个示例使用了PyGTK(和GTK+)。和wxPython的例子相似,这个版本同样也在应用中使用了一个类。对比一下这些GUI应用的相似点和不同点会十分有趣。允许开发者相对容易地切换工具包并不令人十分惊讶。

第5章 GUI编程 - 图24

第5章 GUI编程 - 图25

逐行解释

第1~6行

我们导入了3个不同的模块和包,分别是PyGTK、GTK和Pango。其中,Pango是一个用于文本布局和渲染的库,专门用于 I18N(国际化)。这里需要它是因为它是 GTK+中文本和字体处理的核心。

第8~50行

GTKapp 类中包含了本应用的所有控件。首先创建最顶层的窗口(通过窗口管理器处理程序关闭操作),然后会创建一个垂直方向的布局(VBox),在该布局中包含了主要的控件。这和在wxPython GUI中的做法几乎完全一样。

然而,为了让静态标签和SpinButton、ComboBoxEntry左右相邻(不像wxPython例子中那样上下排列),创建了包含标签-控件对的水平向方框(第 18~35 行),然后把这些 HBox放到包罗万象的VBox中。

在创建完退出按钮并把VBox放到最顶层窗口中之后,要把所有内容渲染到屏幕上。注意,首先创建一个带有空标签的按钮。这样做是为了让 Label(子)对象能够作为按钮的一部分创建。然后在第44~45行中,我们获得了标签的访问权,并将其设置为白色的文本。

我们这么做是因为如果你直接设置前景样式(比如在第40~43行的循环及其附加代码中),会造成前景样式只会影响按钮而不会影响到标签。比如,如果设置前景样式为白色,然后突出显示按钮(通过按下Tab键直至该按钮被选中),你会发现在内部的虚线框中被选定的控件是白色的,但是标签文本仍然会是黑色的,除非你按照第45行的代码那样使用markup进行过修改。

第52~54行

在这里,创建应用,并进入主事件循环。

5.4.5 Tile/Ttk

自创建之初,Tk库就有着良好的声誉,对于构建GUI工具而言,它是一个既灵活又简单的库和工具包。然而,在头十年之后,越来越多的新老开发者感觉到它缺乏新功能、主要改变和升级,开发者会认为它已经过时了,跟不上像wxWidgets和GTK+这样更现代化的工具包了。

Tix尝试通过添加新控件、图像类型和新命令来扩展Tk,从而解决这个问题。其中的一些核心控件甚至使用了原生的 UI 代码,使其与在同一个窗口系统中的其他应用看起来更相似。但是,其努力仅仅是扩展了Tk的功能而已。

21世纪前十年中期,提出了一个更加激进的方法——Tile控件集。Tile重新实现了大多数Tk的核心控件,同时还添加了很多新控件。不仅使原生代码更加普遍,还引入了主题引擎。

主题控件集及其易创建、导入和导出的特点,让开发者(和用户)对应用的视觉外观可以有更强的控制力,并且可以与操作系统及其上运行的窗口系统有更好的无缝结合。Tile 的这个方面足以引人注目,以至于它与Tk 8.5版本的核心结合为Ttk。不同于替代Tk,Ttk控件集是作为原始的核心Tk控件集的辅助而提供的。

Tile/Ttk在Python 2.7和3.1版本中首次亮相。为了使用Ttk,你所使用的Python版本需要能够访问不低于Tk 8.5的版本;当然只要Tile已安装,近期的老版本同样也可以工作。在

Python 2.7+中,Tile/Ttk通过ttk模块导入;而在3.1+版本中,该模块已被吸收进tkinter之下,可以通过tkinter.ttk导入。

在示例5-11和示例5-12中,你可以看到animalTtk.pyw和animalTtk3.pyw的代码,分别对应于Python 2和Python 3版本。无论使用Python 2还是Python 3,UI应用执行后在屏幕上的显示都会和图5-10相似。

示例5-11 Tile/Ttk GUI演示(animalTtk.pyw)

使用Tile工具包的演示应用(当整合到Tk 8.5后,命名为Ttk)。

第5章 GUI编程 - 图26

示例5-12 Tile/Ttk在Python 3下的GUI演示(animalTtk3.pyw)

Python 3下使用Tile工具包的演示(当整合到Tk 8.5后,命名为Ttk)。

第5章 GUI编程 - 图27

第5章 GUI编程 - 图28

第5章 GUI编程 - 图29 图5-10 Tile/Ttk下的动物应用UI

逐行解释

第1~4行

Tk核心控件在Tk 8.4版本中增加了3个新控件。其中一个是本应用中用到的Spinbox(另外两个是LabelFrame和PanedWindow)。其余用到的控件都来自于Tile/Ttk:Label、Button、Combobox,以及Style类(有助于实现控件主题)。

第6~8行

这几行实例化了根窗口和Style对象。其中,Style对象包含一些主题元素,可以供控件选择使用。这有助于为控件定义通用的外观。尽管只用它来创建退出按钮看起来有些浪费,但是你无法直接给按钮指定单独的前景色和背景色。它会强制你以一种规范的方式进行编程。该示例中这个小小的不便之处会在实践中证明一个非常有用的习惯。

第10~26行

剩下代码的主体部分定义(并包装)了整个控件集,这部分与本章介绍过的其他编写本应用的UI都非常相像。其中的控件包括:一个定义应用信息的标签;一个Label和Spinbox的组合,控制可能的数值范围(及增长);一个让用户选择动物的Label-Combobox对;一个退出Button。最后,我们进入GUI主循环。

示例5-12中使用Python 3的代码有着相同的逐行解释。Python 3的版本只是在导入时有所区别:Tkinter在Python 3中重命名为tkinter,且ttk模块变成了tkinter的子模块。

5.5 相关模块和其他GUI

Python中还有一些其他的GUI开发系统可以使用。表5-2列出了一些适当的模块及其对应的窗口系统。

表5-2 Python中可用的GUI系统 第5章 GUI编程 - 图30

(续表) 第5章 GUI编程 - 图31 ① Python 2中为Tkinter,Python 3中为tkinter。

可以在Python wiki站的GUI编程页面找到更多Python相关的GUI工具,该页面地址为:http://wiki.python.org/moin/GuiProgramming。

5.6 练习

5-1 客户端/服务器架构。请描述窗口服务器和窗口客户端的角色。

5-2 面向对象编程。请描述父控件和子控件的关系。

5-3 Label控件。修改tkhello1.py脚本,使用你的自定义消息替代“Hello World!”。

5-4 Label和Button控件。修改tkhello3.py脚本,除了QUIT按钮外,再添加3个新的按钮。按下这3个按钮的任意一个都可以改变文本标签的内容,从而让标签显示为按下的Button(控件)的文本。提示:你将需要3个单独的处理程序,或者有预设参数的一个处理程序(实际上仍然是3个函数对象)。

5-5 Label、Button和Radiobutton控件。修改练习5-4的解决方案,使用3个Radiobutton来控制Label文本的显示。要求有两个按钮:QUIT按钮和update按钮。当单击update按钮时,文本标签显示为被选中的Radiobutton项的文本。如果没有Radiobutton项被选中,则Label保持不变。

5-6 Label、Button和Entry控件。修改练习5-5的解决方案,将Radiobutton替换为一个Entry文本框,并设置其默认值为“Hello World!”(显示在Label的初始化字符串中)。用户可以使用新的文本字符串编辑Entry文本框,单击update按钮后,在Label中更新文本。

5-7 Label和Entry控件及Python I/O。创建一个GUI应用,其中包括一个让用户提供文本文件名的Entry文本框。打开并读取文件内容,并将其显示在Label标签中。选做题(菜单):将Entry文本框替换为一个具有FileOpen选项的菜单,使其弹出窗口,让用户指定要读取的文件。同样地,给菜单添加一个Exit或Quit选项,来增强QUIT按钮。

5-8 简单的文本编辑器。使用之前练习的解决方案创建一个简单的文本编辑器。可以通过从来重建文件或读文件的方式在 Text 控件上显示待用户编辑的文本。当用户退出应用时(要么使用QUIT按钮要么Quit/Exit菜单项),会被提示是否保存修改后再退出。

选做题:在你的脚本中使用拼写检查器的接口,添加一个用于文件内容拼写检查的按钮或菜单项。拼错的单词需要在 Text 控件中使用不同的前景色或背景色突出显示出来。

5-9 多线程聊天应用。之前章节中的聊天程序将在本练习中完成,即创建一个功能全面的多线程聊天服务器。服务器端并不需要GUI,除非你想要创建一个GUI用于前端配置,比如,端口号、服务器名称、到域名服务器的连接等。我们需要创建的是多线程的聊天客户端,使用独立的线程监控用户输入(并向服务器端广播传输的消息),而其他的线程用于接收消息并显示给用户。客户端的前端GUI应该在聊天窗口中包括两部分:一个多行的大块区域用于显示所有对话,以及一个小的文本框用于接受用户输入。

5-10 使用其他GUI。本章中使用不同工具包的示例GUI应用都非常相似,但又都有所不同。尽管不可能让它们都完全相同,但请调整它们使其更加一致。

5-11 使用GUI构建工具。GUI构建工具可以帮助你通过自动化生成模板代码的方式创建GUI应用,从而使你只需要去解决剩下的“硬骨头”。下载一个GUI构建工具,并通过从对应的面板中拖曳控件的方式实现动物 GUI应用。为这些控件添加回调的钩子,以便其行为可以像本章中看到的示例应用那样。

有哪些GUI构建工具可以选择呢?对于wxWidgets,可以考虑PythonCard、wxGlade、XRCed、wxFormBuilder,或者Boa Constructor(已经不再维护)。对于GTK+,可以考虑Glade(以及GtkBuilder)。要了解更多的工具,可以查阅GUI工具wiki页面的“GUI Design Tools and IDEs”部分,其网址是http://wiki.python.org/moin/GuiProgramming。