第3章 tkinter
3.1 第一个tkinter程序
tkinter的程序很容易编写。难点是在后面的布局以及参数的传递。很多控件(Widget)的使用方法都可以从网络上快速的找到。
简单的tkinter窗口程序如下:
import tkinter
root = tkinter.Tk()
root.mainloop()
结果:
只用三行程序就可以构建一个窗口程序,的确非常简单。不过这个程序没有大的用处。整个窗口也是空白的。我们会在后面的章节里面加入其他的控件,让这个窗口具有更多的功能。
详细解读一下上面的程序:
(1)Import tkinter 是引入tkinter模块。所有的控件(Widget)都在里面有定义。
(2)root = tkinter.Tk() 是实例化Tk类。Tk类是顶层的控件类,完成窗口的一系列初始化。有兴趣的可以看tkinter类的__init__.py,看看Tk是如何完成初始化的。
(3)root.mainloop() 是主窗口循环。
3.2 窗口的基本属性
空白的窗口没有什么用处,这节会介绍一些关于窗口的基本属性与功能。
3.2.1 窗口标题
给窗口加一个标题,表明这个窗口是做什么的。一般都是显示程序的名称。或者动态的提示消息,比如打开的文件名称等。
添加标题的语句是:
root.title('Hello')
把这条语句加在mainloop()之前就可以了。
3.2.2 设置窗口的大小
初始化的窗口,是一个很小的窗口,连标题都无法正常显示。因此需要在程序开始的时候设置窗口的大小。
窗口有4个与大小有关的函数:
函数 | 说明 | 备注 |
---|---|---|
winfo_screenwidth() | 整个屏幕宽度 | 是电脑屏幕的宽度 |
winfo_screenheight() | 整个屏幕长度 | 是电脑屏幕的长度 |
winfo_reqwidth() | 窗口宽度 | |
winfo_reqheight() | 窗口长度 |
有了这4个函数,我们就可以轻松的实现窗口大小的调整与在屏幕中间显示。如果没有调整的话,我们创建的tkinter窗口出现在屏幕的位置,每次都是不同的。下面的程序就可以实现调整窗口大小,并在屏幕居中显示:
import tkinter as tk
def get_screen_size(win):
return win.winfo_screenwidth(),win.winfo_screenheight()
def center_window(root, width, height):
screenwidth = root.winfo_screenwidth()
screenheight = root.winfo_screenheight()
size = '%dx%d+%d+%d' % (width, height, (screenwidth - width)/2, (screenheight - height)/2)
root.geometry(size)
root = tk.Tk()
root.title('调整窗口大小')
center_window(root, 400, 320)
root.mainloop()
结果:
要设置窗口的大小与位置,先得到屏幕的大小,然后根据要设定的窗口大小,确定窗口右上角在屏幕上的位置。就是(屏幕的宽度-窗口的宽度)/2和(屏幕的长度-窗口的长度)/2)。然后把这些参数合成一个字符串传入geometry函数,就可以在指定的位置显示指定大小的窗口了。
函数 | 说明 | 备注 |
---|---|---|
geometry(string) | 传入宽度、高度、左上角在屏幕的相对位置 | 是个字符w*h+/-x+/-y |
minsize(x,y) | 最小的窗口尺寸。窗口不会比这个更小。 | |
maxsize(x,y) | 最大窗口尺寸。窗口不会比这个更大 |
3.2.3 窗口内控件的布局
有了窗口就可以在上面放置控件了。控件会在以后的章节中详细说明。本章中,先使用标签控件(Label)来说明布局。所有控件的布局都是继承于同一样的类,所以布局的处理是完全一样。
tkinter 有三种布局模式,pack,grid和place。pack最简单,grid最常用,place用的最少。
3.2.3.1 pack
pack布局非常简单,不用做过多的设置,直接使用一个 pack 函数就可以了。pack方法会从上到下,从左到右的摆放控件。当然也可以指定控件的位置来实现指定的效果,比如让退出按钮在右下角等。
3.2.3.1.1 pack 选项
pack可以使用的选项包括:
名称 | 描述 | 取值范围 |
---|---|---|
expand | 当值为True时,side选项无效。控件显示在父控件中心位置;若fill选项为”both”,则充满父控件的空间。 | “yes”,”no”,”y”,”n” 自然数,浮点数 “no”, 0,True,False (默认值为“no”或0) |
fill | 填充x(y)方向上的空间,当属性side=”top”或”bottom”时,填充x方向;当属性side=”left”或”right”时,填充”y”方向;当expand选项为”yes”时,填充父控件的剩余空间。 | “x”, “y”, “both” |
ipadx, ipady | 控件内部在x(y)方向上间距,默认单位为像素,可选单位为c(厘米)、m(毫米)、i(英寸)、p(打印机的点,即1/27英寸),用法为在值后加以上一个后缀既可。 | 非负浮点数(默认值为0.0) |
padx, pady | 控件外部在x(y)方向上的间距,默认单位为像素,可选单位为c(厘米)、m(毫米)、i(英寸)、p(打印机的点,即1/27英寸),用法为在值后加以上一个后缀既可。 | 非负浮点数(默认值为0.0) |
side | 定义控件贴近在父控件的哪一边上。 | “top”, “bottom”, “left”, “right”(默认为”top”) |
before | 将本控件于所选组建对象之前pack,类似于先创建本控件再创建其他控件。 | 已经pack后的控件对象 |
after | 将本控件于所选组建对象之后pack,类似于先创建选定控件再本控件。 | 已经pack后的控件对象 |
in_ | 将本控件作为所选组建对象的子控件,类似于指定本控件的master为选定控件。 | 已经pack后的控件对象 |
anchor | 控件的摆放方式。默认是居中。左对齐”w”,右对齐”e”,顶对齐”n”,底对齐”s”。w 和e可以与n和s组合使用 | “n”, “s”, “w”, “e”, “nw”, “sw”, “se”, “ne”, “center”(默认为” center”) |
首先这段代码创建一个窗口和一个标签。正常的情况使用pack,此标签就在窗口的顶部居中显示(为了突出显示效果,加上了蓝色的背景)。
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
L = tk.Label(root,text='这是一个标签',bg='blue')
l.pack()
root.mainloop()
修改pack()函数,设置expand=’yes’,可以发现标签选择不仅是左右居中,同时还是垂直居中了。
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
l = tk.Label(root,text='这是一个标签',bg='blue')
l.pack(expand='yes')
root.mainloop()
fill的作用是选择如何填充父控件:
fill = ‘x’: 表示在水平方向充满整个父控件
fill = ‘y’: 表示在垂直方向充满整个父控件
fill = ‘both’:表示充满整个父控件
#fill=x
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
l = tk.Label(root,text='这是一个标签',bg='blue')
l.pack(expand='true',fill='x')
root.mainloop()
#fill=y
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
l = tk.Label(root,text='这是一个标签',bg='blue')
l.pack(expand='true',fill='y')
root.mainloop()
#fill=both
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
l = tk.Label(root,text='这是一个标签',bg='blue')
l.pack(expand='true',fill='both')
root.mainloop()
结果:
这是只有一个控件的情况。如果有两个控件,会是什么样的结果呢?
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
r = tk.Label(root,text='一个红色的标签',bg='red')
b = tk.Label(root,text='一个蓝色标签',bg='blue')
r.pack(expand='true')
b.pack(expand='true',fill='both')
root.mainloop()
结果:
可以看见,两个标签上下平分了窗口。不过有fill选项的会充满下半个窗口,为没有fill选项的,只是在上半个窗口居中显示。
如果上半个窗口没有开启expand选项,那么红色的标签只是在顶端居中显示,剩下的空间由蓝色标签充满。
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
r = tk.Label(root,text='一个红色的标签',bg='red')
b = tk.Label(root,text='一个蓝色标签',bg='blue')
r.pack()
b.pack(expand='true',fill='both')
root.mainloop()
side的作用是放置控件的位置。有四个位置:left,right,top,bottom。可以输入字符串,也可以使用tkinter模块中的常量LEFT,RIGHT,TOP,BOTTOM。
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
b1 = tk.Label(root,text='left')
b1.pack(side=tk.LEFT)
b2 = tk.Label(root,text='right')
b2.pack(side=tk.RIGHT)
b3 = tk.Label(root,text='top')
b3.pack(side=tk.TOP)
b4 = tk.Label(root,text='bottom')
b4.pack(side=tk.BOTTOM)
root.mainloop()
主要作用是如何摆放控件。如果没有指定anchor选项,控件是在父窗口中自顶向下摆放的。anchor一共有八个选项,其实就是我们通常意义上的八个方位。
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
b1 = tk.Label(root,text='1:anochor=\'e\'')
b1.pack(anchor='e')
b2 = tk.Label(root,text='2:anochor=\'w\'')
b2.pack(anchor='w')
b3 = tk.Label(root,text='3:anochor=\'n\'')
b3.pack(anchor='n')
b4 = tk.Label(root,text='4:anochor=\'s\'')
b4.pack(anchor='s')
b5 = tk.Label(root,text='5:anochor=\'ne\'')
b5.pack(anchor='ne')
b6 = tk.Label(root,text='6:anochor=\'se\'')
b6.pack(anchor='se')
b7 = tk.Label(root,text='7:anochor=\'nw\'')
b7.pack(anchor='nw')
b8 = tk.Label(root,text='8:anochor=\'sw\'')
b8.pack(anchor='sw')
root.mainloop()
结果:
anchor和side的作用都是用来定位控件位置的。可以组合起来使用。
ipadx 和 ipady 是内部填充空间的大小。什么意思呢?就是把控件往外扩大的空间。比如控件的宽和长是30 和20。那么使用ipadx和ipady之后,控件的宽和长就是30+ipadx和20+ipady。
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
r = tk.Label(root,bg='red')
r.pack(expand='true',fill='both')
b = tk.Label(r,text='一个蓝色标签',bg='blue')
b.pack(expand='true',ipadx=10,ipady=20)
b2 = tk.Label(r,text='一个蓝色标签',bg='blue')
b2.pack(expand='true')
root.mainloop()
结果:
下面的蓝色标签是对照的。可以看见在x和y的方向上,上面的蓝色标签都扩大了。增加的就是ipadx和ipady。有关单位的描述见上面的表格。一般都是使用默认的单位:像素。
padx和pady是外部间隔,也就是两个控件之间的间隔。
padx就是x方向的间距。参数可以是自然数,也可以是自然数的元组,分别表示左边和右边的间隔。
这段代码是自然数的情况(文本3与文本2和4的间隔是30个像素):
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
b1 = tk.Label(root,text='文本1')
b1.pack(side='left')
b2 = tk.Label(root,text='文本2')
b2.pack(side='left')
b3 = tk.Label(root,text='文本3')
b3.pack(side='left',padx=30)
b4 = tk.Label(root,text='文本4')
b4.pack(side='left')
root.mainloop()
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
b1 = tk.Label(root,text='文本1')
b1.pack(side='left')
b2 = tk.Label(root,text='文本2')
b2.pack(side='left')
b3 = tk.Label(root,text='文本3')
b3.pack(side='left',padx=(10,30))
b4 = tk.Label(root,text='文本4')
b4.pack(side='left')
root.mainloop()
结果:
pady和padx类似,只不过是y方向的间隔。也可以使用元组来表示上下不同的间隔
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
b1 = tk.Label(root,text='文本1')
b1.pack()
b2 = tk.Label(root,text='文本2')
b2.pack()
b3 = tk.Label(root,text='文本3')
b3.pack(pady=(10,30))
b4 = tk.Label(root,text='文本4')
b4.pack()
root.mainloop()
before 和after可以改变pack控件的次序。正常的情况是按照代码的次序pack。不过当需要提前或者推后pack的次序时,可以使用before或者after。比如before=w1,after=w2等等。w1和w2是创建的控件的实例。
下面的代码就是先pack标签b3的。改变了次序。after的情况就不展示了。和before非常类似。
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
b1 = tk.Label(root,text='文本1')
b1.pack()
b2 = tk.Label(root,text='文本2')
b2.pack()
b3 = tk.Label(root,text='文本3被提前到文本2之前pack')
b3.pack(before=b2)
root.mainloop()
结果:
in_是因为in是python的关键字,所以加了一个下划线。in_的作用是设置当前控件的父控件,可以替换掉在控件初始化时候的父控件。本例中,就是用b2替换了root。
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
b1 = tk.Label(root,text='文本1')
b1.pack()
b2 = tk.Label(root,text='文本2',bg='blue')
b2.pack()
b3 = tk.Label(root,text='文本3被放在文本2中')
b3.pack(in_=b2)
root.mainloop()
结果:
3.2.3.1.2 pack 函数
pack的函数包括:
函数名 | 描述 |
---|---|
slaves() | 以列表方式返回本控件的所有子控件对象。 |
propagate(flag) | 设置为True表示父控件的几何大小由子控件决定(默认值),反之则无关。 |
info() | 返回pack提供的选项所对应的数值,返回值为字典类型 |
forget() | 将控件从当前的控件管理器中移除,其实是将控件隐藏并且忽略原有设置,对象依旧存在,可以用pack(option, …)将其显示。 |
location(x, y) | x, y为以像素为单位的点,函数返回此点是否在单元格中,在哪个单元格中。返回单元格行列坐标,(-1, -1)表示不在其中。 |
size() | 返回控件所包含的单元格,揭示控件大小。 |
slaves()函数返回本控件的所有子控件对象。如果不使用pack(),就算已经实例化了子控件,slaves()也不会输出没有pack()的子控件。比如b4就不会输出。而b2和b3会被认为是b1的子控件。
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
b1 = tk.Label(root,text='文本1')
b1.pack()
b2 = tk.Label(b1,text='文本2')
b2.pack()
b3 = tk.Label(b1,text='文本3')
b3.pack()
b4 = tk.Label(b1,text='文本4')
print(b1.slaves())
root.mainloop()
结果:
[, ]
该函数决定父控件的大小是否与子控件有关。如果flag是True则父控件的大小为包括子控件的大小。如果flag是False,则表示父控件的大小与子控件无关。不过geometry()会让propagate()失效,窗口的大小由geometry()决定。
import tkinter as tk
root=tk.Tk()
b1 = tk.Label(root,text='文本1')
b1.pack()
b2 = tk.Label(root,text='文本2')
b2.pack()
b3 = tk.Label(root,text='文本3')
b3.pack()
root.propagate(False)
root.mainloop()
import tkinter as tk
root=tk.Tk()
b1 = tk.Label(root,text='文本1')
b1.pack()
b2 = tk.Label(root,text='文本2')
b2.pack()
b3 = tk.Label(root,text='文本3')
b3.pack()
root.propagate(True)
root.mainloop()
结果:
说明:可以看出True或者False情况下,窗口的大小是不同。
info()返回控件的信息。这些信息以字典的形式返回。
比如上面例子b2的信息如下:
import tkinter as tk
root=tk.Tk()
b1 = tk.Label(root,text='文本1')
b1.pack()
print(b1.info())
root.mainloop()
结果:
{'in': , 'anchor': 'center', 'expand': 0, 'fill': 'none', 'ipadx': 0, 'ipady': 0, 'padx': 0, 'pady': 0, 'side': 'top'}
forget()函数是隐藏控件的。调用之后,该控件从父控件中消失。但是该控件的实例还是存在的。可以用pack()直接恢复显示这个控件。为了演示这个功能,需要增加2个按钮和2个回调函数。回调函数b3_forget()是隐藏标签b3,而b3_pack()是显示标签b3的。这次的代码,会贴上完整的部分。
import tkinter as tk
root=tk.Tk()
root.geometry('300x240')
b1 = tk.Label(root,text='文本1')
b1.pack()
b2 = tk.Label(root,text='文本2')
b2.pack()
b3 = tk.Label(root,text='文本3')
b3.pack()
def b3_forget():
b3.forget()
def b3_pack():
b3.pack()
b4 = tk.Button(root,text='隐藏文本3',command=b3_forget)
b4.pack()
b5 = tk.Button(root,text='显示文本3',command=b3_pack)
b5.pack()
root.mainloop()
#点击’隐藏文本3’按钮后,’文本3’的标签消失。
#再点击’显示文本3’按钮,则显示’文本3’
结果:
注意:此时’文本3’显示在最下面。因为pack()是按照次序来的。以pack()的次序为准,而不是以实例化的次序。
其实这个函数是给grid布局方法使用的。在pack方法中也可以用,不过无论怎么调用,返回的都是(-1,-1)。也就是不在任何单元格内。的确,pack方法就没有单元格的概念,怎么会有符合条件的单元格呢?
size() 函数是返回包括控件的单元格。和location()函数一样,在pack中无效。因为所有控件的返回值都是(0,0)。