图形化用户界面
Python中可以使用的主要GUI工具包:
tkinter
:开源GUI库,作为Python业界开发移动GUI约定俗成的标准,拥有Pmw
,Tix
,PIL
和ttk
等强大的扩展包。wxPython
:一个用于开源wxWidgets
库,最初为C++编平台GUI类框架。适合复杂界面搭建,流行度jinciyutkinter
。PyQt
:一个全面的GUI库,跨越多个平台。相对复杂,但有更多特性。PyGTK
:用于连接GTK的库,可以跨平台。Jython
:一款针对Java的Python应用,可以将Python转换为Java字节码,完成Python语言与Java类库的无缝连接。所以可以使用Java中的swing
和awt
等实现用户界面。IronPython
:一款针对.Net环境和runtime engine
的Python应用,可以将Python代码转换成.Net字节码,同时支持在.Net架构下用Python脚本搭建GUI界面。
tkinter
的特点:- 可访问性:一个轻量级的工具包,非常易用。并且易于扩展。
- 可移植性:能够在不同的平台下提供本地化特色的界面。
- 可用性:它是Python库中的标准模块,自带编译器。
tkinter
扩展包介绍:Pmw
:Python Mega Widgets是用Python建立高级复合组件的扩展工具包。可以提供满足高级GUI开发的需要。该组件的接口与基本dtkinter
组件类似。Tix
:含有40多个高级组件的集合,最初为Tcl/TK编写。可以在tkinter.tix
中得到它。ttk
:一个比较新的组件集,试图区分两类代码,一类负责执行组件行为,另一类负责实现不同的组件外观。该包在tkinter.ttk
中。PIL
:Python Imaging Library是一个开源图像处理扩展包,提供缩放、变形、转换等工具,支持多种图像文件类型。
Python的
tkinter
模块在Tk(最初用Tcl语言编写,由Tcl创立者John Outerhout开发)之上增加了一个软件层,该层允许Python脚本与Tk对话,进行界面的构建和配置,再经路径控制回到处理用户事件的Python脚本。内部调用顺序:从Python脚本到tkinter,再到Tk;GUI事件的响应顺序则是从Tk到tkinter再到Python脚本。Python
tkinter
是在C编写的_tkinter
(实际是它与Tk交互)库之上增加了基于类的交互界面。Python/tkinter程序是事件驱动的。简单的示例:
# 导入Label组件
from tkinter import Label
# 获取Label对象实例
widget = Label(None, text="Hello, tkinter!")
# 布局
widget.pack()
# 开始事件循环
widget.mainloop()
- 创建组件:
- 第一个参数是父组件对象,设置为None表示将新的组件设置在程序的默认顶层窗口。当然也可以将组件放置到其他容器中。
- 第二个参数是配置参数。
http://blog.chriscabin.com/
- tkinter几何管理器只是安排一个或多个组件在容器(父组件)内的位置,顶层窗口和框架(一种特殊组件)都可以作为容器;容器可以嵌套,显示出层级结构。常用的有两种:打包
pack
和网格grid
。 - 在Windows下,
.pyw
和.py
结尾的Python程序在双击运行时表现是不同的,前者不会弹出DOS控制台作为它的标准流。实际上.pyw
文件最好用pythonw
程序打开。 widget.pack(side=TOP)
,TOP
常量在pack
中被调用,它是tkinter的专有名字之一。常量模块由tkinter自动加载。- 对组件使用
pack
时,可以选择组件在父组件中的位置:顶、底、左、右。如果不设置位置,则默认放在父组件的顶部。 root=Tk()
可以得到主窗口,即程序运行时就会出现的窗口。组件如果不设置父组件,则默认依附于自动创建的Tk实例(主窗口)。
# 不用显示地指定root,此时master会被设置为自动创建的Tk对象
Label(text='Hello, Left.').pack(side=LEFT)
Label(text='Hello, Right.').pack(side=RIGHT)
Label(text='Hello, Top.').pack(side=TOP)
Label(text='Hello, Bottom.').pack(side=BOTTOM)
mainloop()
Label(text='Hello, Top.').pack(side=TOP, expand=YES, fill=BOTH)
:expand=YES
要求打包几何管理器为组件扩展空间,通常可以是父组件中任何未被占用的地方。仅使用该项,而不加fill
选项,会自动居中组件。fill
选项:可用来拉伸组件,使其充满分配的空间。fill=Y
表示垂直拉伸,fill=X
表示水平拉伸,fill=BOTH
表示都拉伸。
设置组件选项的另外两种方法:
>>> label.config(text="Good")
>>> label['text'] = "Yahoo"
- 设置窗口标题:
Tk().title("Hello, world")
tkinter
在内部将组件对象交叉连接成一棵可以长期存在的树,该树代表了要显示的对象。所以,即使Python中,对象被垃圾回收,但是在tkinter
中还是会被保留下来。- 添加按钮和回调函数的方法:
def button_demo():
root = Tk()
# 实际上sys.exit和root.quit都能得到相同的效果
# command就是记录按钮单击后的回调行为
Button(text="Exit", command=root.quit).pack(side=TOP, expand=YES)
root.mainloop()
- 回调处理器通常可以是任何可调用的对象:函数、
lambda
表达式生成的匿名函数、类或者类型实例的绑定方法,或者是继承了__call__
运算符重载方法的实例。当按钮按下发生回调时,回调处理器不会接受到任何参数(除了自动为绑定方法生成的self参数),回调处理器需要的状态信息必须以其他方式提供,如全局变量、类实例的属性、间接层提供的额外参数等。 - 使用
lambda
表达式和对象引用来延迟调用:这时一种比较常见的技巧,可以将不带参数的回调映射到另一个由lambda
提供参数的函数上:
def handler(widget, x, y):
widget.configure(text='{} * {} = {}'.format(x, y, y))
def button_demo_2():
label = Label()
label.pack(side=TOP, expand=YES)
# 可以延迟对真正的回调函数的调用
Button(text='Press me!', command=lambda: handler(label, 88, 109)).pack(side=BOTTOM, expand=YES)
mainloop()
- 延迟回调是有必要的(通常都是需要接受参数的回调函数),如果在按钮创建函数中编写回调处理器调用,那么回调函数会在创建按钮的同时被调用。所以,必须要使用中间函数来延迟调用。
- Python的封闭作用域引用模型意味着函数局部作用域内包含lambda函数的局部变量值会被自动保留,并且在按钮按下时使用。
- 默认参数与外部作用域引用不同,它在函数创建时定值,而不是调用时定值。它可以记录函数创建时封闭作用域内名字的值,而不是封闭作用域内某个地方最后分配的值。当函数的作用域是一个模块而不是另外一个函数时,也是如此。这些是在GUI注册回调函数时需要注意的。
>>> def odd():
funcs = []
for c in 'abcdefg':
# 如果是lambda: c,则在后面调用后会出现同样的信息。
# 使用默认参数传值才可以记住c
funcs.append(lambda c=c: c)
return funcs
>>> for x in odd():
print(x())
- 总结:封闭作用域名字引用可以作为默认参数传值的替代之选,但是有条件:封闭作用域中的名字不会在内嵌函数创建后变成其他不知道的值。通常,不能在内嵌函数内部引用封闭作用域的循环变量,然而,在其他大多数情况下,封闭作用域的循环变量在其作用域中只有一个值,这样才可以被自用地使用。但相对来说,类是一种更好的处理方法,它可以用来保存需要的额外信息,也不会出现作用域的问题。
- 使用类中的方法作为回调函数:
class MyGUI:
def __init__(self):
self.x = 100
self.y = 100
self.label = Label()
# 注意,pack返回的是None
self.label.pack(side=TOP, expand=YES)
Button(text="Press to get result", command=self.get_result).pack(side=TOP, expand=YES)
Button(text='Quit', command=sys.exit).pack(side=TOP, expand=YES)
def get_result(self):
self.label.config(text='{} * {} = {}'.format(self.x, self.y, self.x * self.y))
def main():
MyGUI()
mainloop()
- 再来看看另外一种方法来设定回调函数:可调用类对象
class HelloCallable:
def __init__(self):
self.msg = 'Hello __call__ world!'
def __call__(self, *args, **kwargs):
# 重要的是继承了__call__方法后,就可以让类对象像普通函数那样被调用。
print(self.msg)
sys.exit()
# 注意,`HelloCallable()`才可以工作!
Button(text='Quit', command=HelloCallable()).pack(side=TOP, expand=YES)
tkinter
回调协议总结:- 按钮的
command
选项:捕捉按钮按下事件。 - 菜单的
command
选项:捕捉菜单按下事件。 - 滚动条协议:同样使用
command
注册处理器,但是包含一个独特的事件协议,允许它们与被滚动的组件交叉组合,即移动滚动条就会自动移动组件,反之亦然。 - 一般的组件
bind
方法:这种机制可以用来为低级别的界面事件注册回调处理器,比如按键、鼠标等。bind
调用会接收一个事件对象参数(Event
实例),它会提供事件的环境信息。 - 窗口管理器协议:捕捉窗口管理器事件。在顶层窗口的对象管理器里面输入
protocol
方法,为WM_DELETE_WINDOW
设置处理器。 - 预定的事件回调:如定时器过期、输入数据到达、事件循环的空闲状态等。
- 按钮的
在框架(Frame)组件中添加多个组件:
class MyGUI:
def __init__(self):
self.win = Frame()
self.win.pack(expand=YES)
Label(self.win, text="Hello, container world!").pack(side=TOP)
Button(self.win, text="Hello", command=lambda: print('Hello, container world!')).pack(side=LEFT)
Button(self.win, text="Quit", command=self.win.quit).pack(side=RIGHT)
def main():
MyGUI()
mainloop()
- 先打包的组件会在窗口收缩后最后剪裁,所以打包的顺序决定了窗口缩小后那些组件会被先挤出去。
- 一般地,通过将组件依附于框架,再将框架依附于其他框架,我们可以搭建出任意的GUI布局。
- 当组件树显示时,孩子组件出现在父组件内部,并且按照打包顺序好打包位置排列。所以说,组件的打包顺序不仅决定了它们的裁切顺序,还决定了它们在生成的显示中相互间边的位置关系。
packer
布局系统的工作原理:packer
最初拥有整个父组件容器的可用空间;- 随着组件在某条边上被打包,该组件获得了剩余空间要求的一条边,剩余空间缩小;
- 经过先前的打包要求,空间缩小,后来的打包仅要求获得剩余空间中的一条边;
- 组件全部分配后,
expand
选项划分所有剩余空间,fill
选项和anchor
选项在组件分配的空间内拉伸调整组件。
anchor
选项可以定位分配空间中的位置替代fill
选项。可以指定八个方位(指南针:N, NE, NW, W, S等)以及CENTER位置。默认就是CENTER。fill
选项和anchor
选项必须在组件分配到所在空间、完成打包顺序、expand
要求后才可以使用。通过一起使用打包顺序、边、填充和定位,可以产生多种布局和剪裁效果。- 大型GUI界面通常作为Frame子类创建,用回调处理器作为方法。
class HelloFrame(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.pack()
self.data = 20
self.make_widgets()
def make_widgets(self):
w = Button(self, text="hello", command=self.msg)
w.pack(side=LEFT)
def msg(self):
self.data += 1
print('Hello frame world!', self.data)
- 技巧:要改变GUI的行为,可以写一个新类来自定义它的相关部分,而不是改写现有的GUI代码。
tkinter
组件类:
组件类 | 描述 |
---|---|
Label | 显示简单消息区域 |
Button | 带有标签的简单的按钮 |
Frame | 用于盛放和布置其他组件对象 |
Toplevel , Tk | 由窗口管理器管理的新窗口 |
Message | 一个多行标签 |
Entry | 简单的单行文本输入区域 |
Checkbutton | 双状态按钮组件,用于多选 |
Radiobutton | 双状态按钮组件,用于单选 |
Scale | 可衡量位置的滑动组件 |
PhotoImage | 图像组件,全彩图 |
BitmapImage | 图像组件,位图 |
Menu | 菜单相关 |
Menubutton | 打开菜单选项和子菜单的按钮 |
Scrollbar | 滚动其他组件的控件 |
Listbox | 选项列表 |
Text | 多行文本浏览、编辑组件,支持字体 |
Canvas | 图形绘画区域,支持线条、圆圈、照片、文字等 |
- 重要的类和工具:
- 几何管理器:pack
, grid
, place
;
- tkinter连接变量:StringVar
, IntVar
, DoubleVar
, BooleanVar
;
- 高级Tk
组件:SpinBox
, LabelFrame
, PanelWindow
;
- 复合组件:Dialog
, ScrolledText
, OptionMenu
;
- 回调安排:组件的after
, wait
, update
方法;
- 其他:标准对话框、剪切板、bind
和Event
,组件设置选项,用户对话框、模式对话框、动画等。