文章目录
ps:python版本为3.6.2
简介
. 这里主要使用的GUI工具包是Python默认的GUI库Tk,通过接口Tkinter来访问,Tk并不是最新和最好的,也没有包含最强大的GUI构建模块集,但是足够易用。
查看自己的解释器是否安装了Tkinter的方法是import一下,不过在python3的版本下应该是自动安装的,另外模块的名字是tkinter。
Tkinter和Python编程
Tkinter模块:添加Tk到应用中
. 让一个GUI应用能够启动和运行起来需要以下5个步骤:
1.Tkinter模块;
2.创建一个顶层窗口对象,以容纳整个GUI应用;
3.在顶层窗口对象上(或其中)构建所有的GUI组件(及功能);
4.通过底层的应用代码将这些组件连接起来;
5.进入主事件循环。
GUI编程介绍
. 就像画家作画一样,首先需要的是一张干净的画布,然后才能在上面画出不同的画面,这个基础就像是Tkinter中的顶层窗口对象。
窗口和控件
. 在GUI编程中,顶层的根窗口对象包含组成GUI应用的所有小窗口对象,它们可能是标签、按钮、列表框等,这些独立的GUI组件称为控件。一般创建一个顶层窗口所用的语句是:
top = tkinter.Tk()
. 这里返回的对象通常称为根窗口,顶层窗口是那些在应用中独立显示的部分。GUI程序中可以有多个顶层窗口,但是只能有一个根窗口。
控件可以独立存在,也可以作为容器存在,如果一个控件包含其他控件,就可以将其认为是那些控件的父控件,那些控件也认为是这个控件的子控件。
通常,控件有一些相关的行为,比如按钮可以点击,这些用户行为被称为事件,而GUI对这些事件的响应称为回调。
事件驱动处理
. 一个GUI应用从开始到结束就是通过整套事件体系来驱动的,这种方式称为事件驱动处理。
比如说最简单的鼠标移动的例子。假设鼠标处于静止状态,然后被手移动到另一个位置,鼠标移动的行为会被复制到屏幕的光标上,于是看起来像是根据你的手来移动的,系统必须处理这些鼠标移动事件来绘制窗口上的光标移动,当鼠标再次停下来,没有事件需要处理的时候,屏幕会重新恢复到闲置状态。
布局管理器
. Tk有三种布局管理器来帮助控件集进行定位。
第一种称为Placer:做法很直接,开发者提供控件的大小和摆放位置,然后管理器就会将其摆放好。问题是开发者必须对所有控件进行这些操作,这会加大开发者的负担;
第二种是Packer,这将是主要使用的:它会将控件填充到正确的位置(即指定的父控件中),然后对于之后的控件,回去寻找剩余的空间填充,就像是打包行李的过程;
第三种是Grid:基于网格坐标来指定控件的位置,Grid会在它们的网格位置上渲染GUI应用中的每个对象。
当所有控件摆放好后,就可以让应用进入到无限主循环中,代码一般如下:
tkinter.mainloop()
. 一般这是程序的最后一句话。当程序进入到主循环后,GUI就从这里开始接管程序,所有其他行为都通过回调来处理,甚至包括退出。
Tk控件
. 以下是书中提到的控件:
控件 | 描述 |
---|---|
Label | 用于包含文本或图像 |
Button | 与 Label 类似,但提供额外的功能,如鼠标悬浮、按下、释放以及键盘活动/事件 |
Canvas | 提供绘制形状的功能(线段、椭圆、多边形、矩形),可以包含图像或位图 |
Checkbutton | 一组选框,可以勾选其中的任意个(与 HTML 的 checkbox 输入类似) |
Entry | 单行文本框,用于收集键盘输入(与 HTML 的文本输入类似) |
Frame | 包含其他控件的纯容器 |
LabelFrame | 标签和框架的组合,拥有额外的标签属性 |
Listbox | 给用户显示一个选项列表来进行选择 |
Menubutton | 用于包含菜单(下拉、级联等) |
Menu | 按下Menubutton 后弹出的选项列表,用户可以从中选择 |
Message | 消息。与 Label 类似,不过可以显示成多行 |
PanedWindow | 一个可以控制其他控件在其中摆放的容器控件 |
Radiobutton | 一组按钮,其中只有一个可以“按下”(与 HTML 的 radio 输入类似) |
Scale | 线性“滑块”控件,根据已设定的起始值和终止值,给出当前设定的精确值 |
Text | 多行文本框,用于收集(或显示)用户输入的文本(与 HTML 的 textarea 类似) |
Scrollbar | 为 Text、Canvas、Listbox、Enter 等支持的控件提供滚动功能 |
Spinbox | Entry 和 Button 的组合,允许对值进行调整 |
Toplevel | 与 Frame 类似,不过它提供了一个单独的窗口容器 |
Tkinter示例
Label、Button和Scale控件
. 以下是一个包含标签、按钮以及滑块的GUI程序,通过滑块调节标签中字的大小,通过按钮退出程序:
import tkinter
def resize(ev = None): #滑动滑块时的回调函数
label.config(font = 'Helvetica -%d bold' % scale.get())
top = tkinter.Tk()
top.geometry('250x150') #设置根窗口的大小,是x,不是*
label = tkinter.Label(top,text = 'Hello World',font = 'Helvetica -12 bold')
label.pack(fill = tkinter.Y,expand = 1)
scale = tkinter.Scale(top,from_ = 10,to = 40,orient = tkinter.HORIZONTAL,
command = resize)
scale.set(12) #滑块位置的初始值设置
scale.pack(fill = tkinter.X,expand = 1)
quit = tkinter.Button(top,text = '退出',command = top.quit,
activeforeground = 'white',
activebackground = 'red')
quit.pack(fill = tkinter.X,expand = 1)
tkinter.mainloop()
. 控件一般都有默认参数,如果不在意可以不用都写,另外在按钮和滑块的pack函数中对fill赋值,表示填充剩余的水平空间, expand 参数则会引导它填充整个水平可视空间,将按钮拉伸到左右窗口边缘。command参数表示给控件安装的回调函数。
在pack没有收到其它指示的时候,控件都默认是垂直排列的,如果想要水平布局则需要创建一个新的Frame对象来添加按钮。
偏函数应用示例
. 偏函数是函数式编程一系列改进中的一部分。使用偏函数,可以有效的“冻结”那些预先确定的参数来缓存函数参数,然后再运行时,当获得需要的剩余参数后,可以将它们解冻,传递到最终的参数中,从而使用最终确定的所有参数去调用函数。
偏函数不止可适用于函数,也可以使用于可调用对象(重载了圆括号的那种类或结构体吧),对于那些反复使用相同的参数的可调用对象,使用偏函数会更合适。
GUI编程是一个很好的偏函数用例,因为通常会希望界面的风格能够一致,比如按钮拥有一致的前景色和背景色。
以下是一个偏函数的示例,通过交通路标的的不同颜色方案来展示,分为三种:严重、警告和提醒,三种路标有不同的文字、前景色、背景色和回调函数:
from functools import partial as pt #偏函数相关库
from tkinter import Tk,Button,X,messagebox
WARN = 'warn'
CRIT = 'crit'
REGU = 'regu'
SIGNS = {
'do not enter':CRIT,
'railroad crossing':WARN,
'55\hspeed limit':REGU,
'wrong way':CRIT,
'merging traffic':WARN,
'one way':REGU
}
#点击按钮的回调
critCB = lambda : messagebox.showerror('Error','Error Button Pressed')
warnCB = lambda : messagebox.showwarning('Warn','Warn Button Pressed')
reguCB = lambda : messagebox.showinfo('Regu','Regu Button Pressed')
top = Tk()
top.title('Road Signs')
Button(top,text = 'QUIT',command = top.quit,bg = 'red',fg = 'white').pack()
MyButton = pt(Button,top)
CritButton = pt(MyButton,command = critCB,bg = 'white',fg = 'red')
WarnButton = pt(MyButton,command = warnCB,bg = 'goldenrod1')
ReguButton = pt(MyButton,command = reguCB,bg = 'white')
for eachsign in SIGNS:
signtype = SIGNS[eachsign]
cmd = "{0}Button(text = '{1}').pack(fill = X,expand = True)".format(
signtype.title(),eachsign)
eval(cmd)
top.mainloop()
. 其中使用了两阶偏函数,第一阶模板化了Button类和根窗口,意味着每次调用MyButton时都会调用Button类,并将top作为它的第一个参数。将它“冻结”为MyButton。
第二阶偏函数使用第一阶偏函数,并对其模板化,创建不同的按钮时,它就会调用适当的MyButton。最后的eval() 函数用来执行一个字符串表达式。
中级Tkinter示例
. 接下来是一个更复杂的示例,这是一个目录树遍历工具,除了之前的空间还使用到了列表框、文本框和滚动条。此外,还增加了鼠标单击、键盘按下、滚动操作等回调函数:
import os
from time import sleep
from tkinter import *
class DirList(object):
def __init__(self,initdir = None):
self.top = Tk()
self.label = Label(self.top,text = 'Directory Lister v1.1')
self.label.pack()
self.cwd = StringVar(self.top) #用于保存当前所在的目录名
self.dirl = Label(self.top,fg = 'blue',font = ('Helvetica' ,12,'bold'))
self.dirl.pack()
self.dirfm = Frame(self.top) #中间部分,包括显示列表,滑块
self.dirsb = Scrollbar(self.dirfm)
self.dirsb.pack(side = RIGHT,fill = Y)
self.dirs = Listbox(self.dirfm,height = 15,width = 50,
yscrollcommand = self.dirsb.set)
#通过使用 Listbox 的 bind()方法,
#Listbox 的列表项可以与回调函数(setDirAndGo)连接起来
self.dirs.bind('<Double-1>',self.setDirAndGo)
self.dirsb.config(command = self.dirs.yview)
self.dirs.pack(side = LEFT,fill = BOTH)
self.dirfm.pack()
#输入框,可以从这里手动输入目录跳转
self.dirn = Entry(self.top,width = 50,textvariable = self.cwd)
#绑定了回车键,除了点击按钮,敲击回车也能达到同样效果
self.dirn.bind('<Return>',self.doLS)
self.dirn.pack()
self.bfm = Frame(self.top)
self.clr = Button(self.bfm,text = 'clear',
command = self.clrDir,
activeforeground = 'white',
activebackground = 'blue')
self.ls = Button(self.bfm,text = 'List Directory',
command = self.doLS,
activeforeground='white',
activebackground='blue')
self.quit = Button(self.bfm, text='quit',
command=self.top.quit,
activeforeground='white',
activebackground='red')
self.clr.pack(side = LEFT)
self.ls.pack(side=LEFT)
self.quit.pack(side=LEFT)
self.bfm.pack()
if initdir:
self.cwd.set(os.curdir)
self.doLS()
#清空输入框
def clrDir(self,ev = None):
self.cwd.set('')
#设置要遍历的目录
def setDirAndGo(self,ev = None):
self.last = self.cwd.get()
self.dirs.config(selectbackground = 'red')
check = self.dirs.get(self.dirs.curselection())
if not check:
check = os.curdir
self.cwd.set(check)
self.doLS()
def doLS(self,ev = None):
#安全检查
error = ''
tdir = self.cwd.get()
if not tdir:
tdir = os.curdir
if not os.path.exists(tdir):
error = tdir + 'no such file'
elif not os.path.isdir(tdir):
error = tdir + 'is not a dir'
if error:
self.cwd.set(error)
self.top.update()
sleep(2)
if not (hasattr(self,'last') and self.last):
self.last = os.curdir
self.cwd.set(self.last)
self.dirs.config(selectbackground = 'LightSkyBlue')
self.top.update()
return
#如果一切正常,就会调用 os.listdir()获取实际文件列表并在 Listbox 中进行替换
self.cwd.set('Fetching directory contents...')
self.top.update()
dirlist = os.listdir(tdir)
dirlist.sort()
os.chdir(tdir)
self.dirl.config(text = os.getcwd())
self.dirs.delete(0,END)
self.dirs.insert(END,os.curdir)
self.dirs.insert(END, os.pardir)
for eachFile in dirlist:
self.dirs.insert(END,eachFile)
self.cwd.set(os.curdir)
self.dirs.config(selectbackground = 'LightSkyBlue')
def main():
d = DirList(os.curdir)
mainloop()
if __name__ == '__main__':
main()
. 其中bind函数起到绑定的作用,绑定意味着将一个回调函数与按键、鼠标操作或一些其他事件连接起来,当用户发起这类事件时,回调函数就会执行。当双击 Listbox 中的任意条目时,就会调用 setDirAndGo()函数。而 Scrollbar 通过调用 Scrollbar.config()方法与 Listbox 连接起来。