提示:下滑文章左侧可以查看目录!
1 走进tkinter世界
1.1 认识tkinter
tkinter是一个GUI开发模块,是Tcl/Tk语言在Python上的接口,可以在大部分操作系统上运行。tkinter非常的简单而且好用。tkinter模块是自带的Python模块,如果在安装Python的时候勾选了Tcl/Tk这个选项,那么使用tkinter不会有任何问题。
导入模块非常简单,但是Python3和Python2略有不同,Python3是这样的:
import tkinter
本文的示例以Python3为准,而Python2是这样的:
import Tkinter #Tkinter开头的t是大写的
不过tkinter这个名字非常长,所以我们通常习惯这么导入:
import tkinter as tkfrom tkinter import *
如果导入时候就出现了错误,提示找不到_tkinter这一模块,或者调用里面的方法时出现版本错误提示,可能是因为安装时不到位,没有勾选Tk/Tcl这一选项。在安装包中选择Modify,更改Python的安装即可
接下来让我们了解一下自己tkinter的版本:
import tkinterprint(tkinter.TkVersion)
最好是使用8.5 Version以上的tkinter,功能比较全面一些。
1.2 tkinter的坐标系与颜色格式
坐标系
组件的排放,鼠标事件等功能都少不了坐标。tkinter的坐标系和数学上习惯用的坐标系略有不同,和pygame的坐标系是一样的。
以左上角为起点,x轴向右延伸,y轴向下延伸。在窗口中,容器的左上角是(0, 0),不包括窗口的标题栏和菜单栏。
颜色
当在tkinter中设置颜色时,可以用两种表示颜色的方式:一种是颜色的名称,比如”green”, “brown”;另一种是颜色的十六进制形式,比如”#00ffff”。遗憾的是,tkinter不支持颜色RGB元组形式,不过可以把它转换成十六进制形式。
这种十六进制形式相当于:”#”+R的十六进制+G的十六进制+B的十六进制。比如(255, 255, 255)是纯白,转换成十六进制形式就变成了#ffffff。
tkinter也有一种特殊的颜色名称,叫做SystemButtonFace,是一种浅灰色,是组件的默认背景颜色。
1.3 创建根窗口
根窗口是最主要的一个窗口,根窗口最好只有一个,因为一个Tk就是一个新的Tcl/Tk解释器,解释器并不需要太多。
根窗口使用tkinter中的Tk方法创建。在窗口中,我们可以添加各种各样的控件,也称组件(widget),比如按钮、文本输入框等,我们将在后期介绍。窗口也可以有一些子窗口。当父窗口关闭后,所有的子窗口会跟着关闭,但是子窗口关闭,父窗口不会关闭。
from tkinter import *root = Tk()mainloop()
这一段代码创建一个窗口,并且循环显示这个窗口。mainloop方法,可以让窗口循环显示,否则运行时窗口一闪就没了。一定不要忘记mainloop!mainloop也可以用while True: root.update()这一段代替,不过mainloop更加常用一些。mainloop也可以作为窗口的一个方法,即root.mainloop()。
这段代码创建了一个独立的窗口,默认标题叫tk,你可以在下面的任务栏找到它。同样,你也可以自由拖拽它的位置,改变窗口的大小。也可以把它关闭、最小化、最大化。
Tk(screenName=None, baseName=None, className=’Tk’, useTk=1, sync=0, use=None)
Tk有一个参数叫做className,允许你改变窗口标题。但是这样改变标题有一个bug,就是窗口标题的首字母会自动小写,因此不推荐你这么做,而应使用title方法。Tk的参数并不常用,但有一些比较基础常用的方法,更多的方法,请参见2.2.14。
方法 | 使用方法 |
title(string=None) | 设置窗口的标题,同时返回窗口标题 |
geometry(newGeometry=None) | 设置窗口的尺寸大小,同时返回当前窗口尺寸 |
iconbitmap(bitmap=None) | 设置窗口的图标,需指定图标文件(*.ico)的位置 |
resizable(width=None, height=None) | 设定是否能够改变窗口的宽和高尺寸 |
destroy() | 销毁窗口,也就是把窗口关掉 |
下面看一个示例,演示了tk中一些常用的窗口操作:
from tkinter import *root = Tk()root.title("我的窗口")root.iconbitmap("my_icon.ico")root.geometry("500x500")root.resizable(False, False)mainloop()
可以看出,窗口被设置了标题”我的窗口”,图标也变成了自定义的图标。由于resizable的设定,这个窗口无法改变大小,只能保持在500×500。
下面着重讲一下geometry方法。这个方法不仅可以设置窗口的尺寸,也可以设置窗口在电脑屏幕上的位置。给定参数的格式是:widthxheight+x+y。root.geometry(“300×100+20+50”)代表的就是把root窗口设置为300×100的尺寸,与屏幕最左边相隔20像素,与屏幕最上方相隔50像素。可以只设置窗口的尺寸,即widthxheight;也可以只设置窗口的位置,即+x+y。geometry还有一些用法,在讲Wm类的时候会介绍。
窗口有一个默认的背景颜色,同样也是大多数tk组件的颜色。这个颜色是一种浅灰色,在tk内部称作SystemButtonFace,只能在tk中使用,其他模块是不支持这个颜色的。如果要改变窗口的背景,可以使用窗口的config方法,也可以写作configure方法。大部分控件都支持这个方法,用来定义控件后改变它的属性。
from tkinter import *root = Tk()root.config(bg="blue")root.mainloop()
一个纯蓝色的窗口就出现了。
1.4 组件
tkinter支持很多组件,可以帮助你完成一些功能。组件根据坐标被排列在容器(container)中,窗口的界面是该窗口中最大的容器。
tkinter的组件有:
- Label:标签控件,用来在窗口上显示文本和图片
- Message:消息控件,用来显示多行文本,与Label功能类似
- Button:按钮控件,用户可以点击按钮,点击事件将会传递给设置的回调函数
- Entry:文本输入框控件,用户可以输入文字,但只能在一行输入
- Text:多行文本输入框控件,用户可以输入多行文字,自由换行
- Canvas:画布控件,可以在上面显示基本图形、文字、图片
- Frame:框架控件,作为一个小容器,相当于给组件分组。
- LabelFrame:文字框架控件,和Frame不同的是,框架外面多了文本提示
- Menu:菜单控件,在窗口上显示菜单,或定义弹出式菜单。
- Menubutton:菜单按钮控件,是Button的样子,点击后弹出一个菜单。
- Checkbutton:多选按钮,用户可以勾选或取消勾选。
- Radiobutton:单选按钮,用户可以在同类的Radiobutton中选择一个,无法取消勾选
- Listbox:列表框组件,可以显示一个字符串的列表
- Scrollbar:滚动条控件,用来添加一个滚动条控制滚动
- Scale:尺度条控件,用来添加一个数字滑块,用户可以滑动调整数值。
- Spinbox:数字选值框控件,用户既可以输入数字,也可以按调节按钮调整数值。
- OptionMenu:选项菜单,用户可以从下拉菜单中选择一个值,但是不能自己输入。
- PanedWindow:分栏容器控件,和Frame类似,但是有更多的功能设定,比如用户可以调节大小
- Toplevel:上层窗口控件,可以定义某个窗口的子窗口。
tkinter还有一些子模块,如ttk,messagebox,colorchooser,filedialog等。
ttk中有一些扩展组件,里面有一些和主模块一样的控件,但是样子要不同。ttk有一个最大的特点,组件的字体、颜色等功能不能直接修改,而是要用ttk.Style形式修改,后期会讲述。而tkinter主模块中可以直接指定组件的颜色、字体等样式。所以,如果在from tkinter import *后继续导入from tkinter.ttk import *,就会覆盖tkinter.ttk与tkinter主模块中相同的组件,要改变字体和颜色只能使用Style的形式。这一点千万不能弄错。
tkinter.ttk的扩展组件有:
- Combobox:组合选择框控件,用户可以自己在输入框中输入内容,也可以在下拉列表中选择。
- Notebook:笔记本控件,添加多个Frame选项卡,用户可以在不同选项卡之间切换。
- Progressbar:进度条控件,显示一个加载时的进度条
- Separator:分割线控件,显示一条垂直或水平的分割线。
- Treeview:树状图控件,显示一个表格或是树状图。
- Sizegrip:尺寸调整控件,显示一个调整窗口尺寸的按钮。
组件都有一个参数,用来定义这个组件的父容器,大多数组件的类也都有一些共同的参数,这些参数以**kw的形式传递,如:
参数名称 | 作用 |
master | 组件的父容器,一般必选 |
name | 组件在Tcl/Tk内部的名称 |
bg background | 改变组件的背景(ttk没有) |
fg foreground | 改变组件的前景色,一般是文本颜色 |
width | 组件的宽,单位是像素(在文本输入类组件中,单位是字符数量) |
height | 组件的高,单位是像素(在文本输入类组件中,单位是字符数量) |
cursor | 鼠标放上组件的光标样式 |
relief | 组件边框样式 |
state | 组件状态,可设置为normal(普通样式),disabled(禁用),active(激活),readonly(只读)。其中normal和disabled所有组件都支持,而active,readonly只有部分组件支持。 |
takefocus | 组件是否能获取焦点 |
bd borderwidth | 组件边框的宽度 |
activebackground | 组件激活时的背景色 |
activeforeground | 组件激活时的前景色 |
disabledforeground | 组件禁用时的前景色 |
disabledbackground | 组件禁用时的背景色 |
highlightcolor | 高亮(组件获得焦点)时的边框颜色 |
highlightthickness | 高亮边框宽度 |
exportselection | 这个是所有含有输入功能的组件的共有参数,表示选中的内容是否可以被Misc.selection_get方法检测到,参见后文对Misc类的介绍 |
这意味着,你可以这么定义一个组件:Label(master=root, bg=”blue”)。组件的类也有一个cnf参数,传给这个参数一个字典,也可以达到定义组件的效果。上面也可以写作:Label(cnf={“master”:root, “bg”:”blue})。
组件也有一些共同的方法:
方法名称 | 作用 |
after(ms, func=None) | 等待ms毫秒后执行一次func |
bind(sequence=None, func=None) | 绑定事件,检测到事件后调用func |
unbind(sequence) | 解除绑定事件 |
update() | 刷新组件,一般不需要手动调用 |
cget(key) | 返回关键字参数key的值,如:root.cget(“bg”)返回root的背景色 |
configure(**kw) | 也写作config,重新改变组件的关键字参数设置 |
destroy() | 销毁组件 |
focus_set() | 设置输入焦点 |
2 tkinter主模块
tkinter有一系列的子模块,这里介绍主模块内一些方法的使用方式。
2.1 Label
本节中你将了解tk中最常用也是最简单的组件:标签。
参考资料:Python Tkinter 标签控件(Label) | 菜鸟教程
Label(master=None, cnf={}, **kw)
参数名称 | 作用 |
text | 显示的文本 |
font | 文本的字体 |
image | 显示的图片 |
bitmap | 显示的位图,和image只能指定一个 |
textvariable | 绑定的文本变量 |
compound | 当文本和图片同时显示时,图片位于文本的方位,可以是top, bottom, left, right, center,如:设置为top表示图片位于文本上方 |
padx | 标签内容与左右的间距 |
pady | 标签内容与上下的间距 |
anchor | 文本靠标签哪个方向显示,可以是n,s,w,e,ne,nw,sw,se,center,即北、南、西、东、东北、西北、西南、东南、中间,默认靠中间显示 |
justify | 文本的对齐方式,可以是left, right, center,默认是center |
wraplength | 自动换行字符数量,到达数量后文本会自动换一行显示 |
创建Label
from tkinter import *root = Tk()root.geometry("200x200")lab = Label(root, text="Hello, Tkinter!")lab.pack()mainloop()
创建Label的时候,先要指定master,即摆放组件的父容器。所有组件都需要这样,不然tk可能不清楚你要排放在哪个容器上面。text指定显示的文本。这样就定义好了一个Label对象,赋值给lab。lab.pack()的意思是,将定义的Label摆放到父容器上面。所有的组件都需要在定义后摆放到窗口上,不然组件显示不了。至于排放位置的设置,之后会讲述到。
运行效果如下:
relief参数
relief参数设定组件的样式,大多数组件都支持relief参数。
from tkinter import *root = Tk()root.geometry("200x200")lab = Label(root, text="Hello, Tkinter!", relief="sunken")lab.pack()mainloop()
运行效果:
relief参数指定了组件边框的样式,一共有6种relief,分别是flat, groove, raised, ridge, solid, sunken。Label的默认relief是flat。这6种relief的效果如下:
cursor参数
参考资料:【Python cursor指针】——Python Tkinter Cursor鼠标指针属性值
cursor参数指定鼠标移动到组件上时,光标的样子。光标样式有很多,这里不再赘述,可以参考下面这个示例,它会显示光标所有的样式。鼠标放在对应Label上,会显示出当前光标样式。
cursorList = ['arrow', 'xterm', 'watch', 'hand2', 'question_arrow', 'sb_h_double_arrow', 'sb_v_double_arrow', 'fleur','crosshair', 'based_arrow_down', 'based_arrow_up', 'boat', 'bogosity', 'top_left_corner','top_right_corner', 'bottom_left_corner', 'bottom_right_corner', 'top_side', 'bottom_side', 'top_tee','bottom_tee', 'box_spiral', 'center_ptr', 'circle', 'clock', 'coffee_mug', 'cross', 'cross_reverse','diamond_cross', 'dot', 'dotbox', 'double_arrow', 'top_left_arrow', 'draft_small', 'draft_large','left_ptr', 'right_ptr', 'draped_box', 'exchange', 'gobbler', 'gumby', 'hand1', 'heart', 'icon','iron_cross', 'left_side', 'right_side', 'left_tee', 'right_tee', 'leftbutton', 'middlebutton','rightbutton', 'll_angle', 'lr_angle', 'man', 'mouse', 'pencil', 'pirate', 'plus', 'rtl_logo', 'sailboat','sb_left_arrow', 'sb_right_arrow', 'sb_up_arrow', 'sb_down_arrow', 'shuttle', 'sizing', 'spider','spraycan', 'star', 'target', 'tcross', 'trek', 'ul_angle', 'umbrella', 'ur_angle', 'X_cursor']#所有的光标样式from tkinter import *root = Tk()for i in range(len(cursorList)):cursor = cursorList[i]Label(text=cursor, cursor=cursor, relief="groove").grid(column=i // 20, row=i % 20, sticky="we") #后面会讲到grid,是一种排放组件方式root.mainloop()
介绍一下常见的光标样式。不同的主题和系统可能有所不同。
arrow | |
xterm | |
watch | |
hand2 | |
question_arrow | |
sb_h_double_arrow | |
sb_v_double_arrow | |
fleur | |
crosshair |
font参数
font参数指定文本的字体,大多数带有文本的组件都支持这个参数。font参数指定字体的样式、大小、以及是否有加粗下划线等特殊样式。font参数可以是tkinter.font.Font对象,也可以只给一个字体名称或是字体大小数值,或是给一个元组。
lab = Label(root, text="Hello!", font=("黑体", 20)) #字体为黑体,大小20;顺序不能颠倒lab = Label(root, text="Hello!", font=20) #只设置大小20lab = Label(root, text="Hello!", font="黑体") #只设置字体为黑体
字体的元组后面还可以加上字体的特殊样式,一共有bold(加粗), italic(斜体),underline(下划线),overstrike(删除线)几种。可以叠加设置。
Label(root, text="加粗", font=("黑体", 20, "bold")).pack()Label(root, text="斜体", font=("黑体", 20, "italic")).pack()Label(root, text="下划线", font=("黑体", 20, "underline")).pack()Label(root, text="删除线", font=("黑体", 20, "overstrike")).pack()Label(root, text="叠加使用", font=("黑体", 20, "bold", "italic", "underline", "overstrike")).pack()
运行效果:
bitmap参数
bitmap参数指定添加位图,即内置图标,有error, info, hourglass, questhead, question, warning, gray12, gray25, gray50, gray75。下面的示例列举了所有bitmap:
from tkinter import *root = Tk()bitmaps = ["error", "info", "hourglass", "questhead", "question", "warning", "gray12", "gray25", "gray50", "gray75"]for bitmap in bitmaps:Label(root, text=bitmap, bitmap=bitmap, compound="left").pack()mainloop()
compound的意思是:图片置于文字的位置,上面的参数解释有。
运行效果:
image参数
image参数在Label中添加图片。这个图片是一个tk.PhotoImage对象,支持的格式只有*.gif, *.ppm, *.pgm,较新版的tk支持显示*.png。注意:直接在图片文件后面改后缀不会改变图片本身的文件类型!更需要强调的一点是,图片对象必须要赋值给一个全局变量,或者是类的实例变量之类的,保证不会被Python机制回收,否则图片无法正确显示。
image = tkinter.PhotoImage(file="图片名称")
这段代码建立了一个tk图片对象,现在需要把它传递给Label。
image = PhotoImage(file="monster.gif")Label(root, image=image, text="It's a monster.", compound="top").pack()
运行效果:
但是,如果想要显示更多的图片文件格式,比如*.jpg该怎么办呢?这时候需要使用pillow工具。这是一个第三方模块,需要用pip安装:pip install pillow,导入时import PIL。
from tkinter import *from PIL import Image, ImageTkroot = Tk()root.geometry("200x200")image = ImageTk.PhotoImage(Image.open("monster.png"))Label(root, image=image, text="It's a monster.", compound="top").pack()mainloop()
运行效果:
这样,我们用PIL工具成功显示了png图片(不过版本较新的Tk本来也可以显示png图片)。也需要注意,在使用tkinter.PhotoImage的时候,需要指定file=”文件名”这个关键字参数,但使用PIL工具则不需要指定file关键字参数。
tkinter也有一些内部的图片,可以通过字符串传递给image。
包括:”::tk::icons::error”,”::tk::icons::information”,”::tk::icons::question”,”::tk::icons::warning”。
它们的效果如下:
from tkinter import *root = Tk()for image in ["::tk::icons::error","::tk::icons::information","::tk::icons::question","::tk::icons::warning"]:Label(root, text=image, image=image, compound="top").pack()mainloop()
这些名字为什么这么复杂呢?其实,::是Tcl语言命名空间的表示方式,这里不多讲。
textvariable参数
tkinter提供了一些变量对象,可以绑定到组件,可以设置它们的值。绑定的组件会随着设置而刷新。这些对象有StringVar()文本变量,IntVar()整数变量,DoubleVar()浮点数变量,BooleanVar()布尔值变量。
建立一个tkinter变量的方法是:
var = tkinter.StringVar()
然后就可以设置变量的值:
var.set(value)
也可以获取变量的值:
sth = var.get()
StringVar中可以设置文字,IntVar可以设置整数,BooleanVar可以设置True和False,等等。
Label绑定textvariable,只需要添加一个参数textvariable=var,后期如果想要更改Label中的内容,可以执行var.set(value)来更改。当然也可以使用lab.config(text=value)这样的方式。
2.2 Button
Button即按钮,可以绑定一个回调函数,用户点击时将会执行这个函数。
参考资料:Python Tkinter 按钮组件 | 菜鸟教程
Button(master=None, cnf={}, **kw)
很多tk组件都有共通性,比如带有文本的组件,大多都有text, font, textvariable这些参数。Button的参数和Label基本类似,也支持text, image, bitmap等功能。Button也有relief,按钮默认的relief是raised。与Label最不同的是,它还可以绑定一个点击事件。
参数名称 | 作用 |
command | 点击按钮时运行(是一个方法) |
repeatdelay | 延迟多少毫秒(1000ms=1s)后进行按钮的持续触发 |
repeatinterval | 按钮持续触发的间隔时长(毫秒) |
overrelief | 鼠标经过时按钮的relief样式 |
常用方法:
方法 | 作用 |
invoke | 调用Button的command(disabled无效) |
flash | 使Button闪烁几次(在normal和active几次切换) |
创建Button
下面我们就来创建一个Button,它可以绑定一个回调函数,点击时在屏幕上打印”你点了一下按钮”。
from tkinter import *root = Tk()def callback():print("你点了一下按钮")button = Button(root, text="按钮", command=callback)button.pack()mainloop()
运行后点击按钮,可以看到如下输出。按钮可以多次点击。
activeforeground和activebackground参数
如果鼠标长按按钮,那么按钮不会被触发,而是松开鼠标后触发。这时候,按钮处于一种激活状态。我们可以设置激活时按钮的前景和背景颜色:
Button(root, text="按钮", activeforeground="blue", activebackground="yellow")
如上面这段代码,把激活时的前景色设为blue(蓝色),背景色则设为yellow(黄色)。点击时呈现这样的效果:
repeatdelay和repeatinterval参数
这两个参数可以用于按钮的持续触发。用户长按在按钮上,经过repeatdelay毫秒的延迟后,按钮将会重复触发,每次触发的间隔是repeatinterval毫秒。
from tkinter import *root = Tk()root.geometry("200x200")def addnum():num = int(b.cget("text")) #获取组件的参数选项b.config(text=str(num + 1))b = Button(root, text="0", command=addnum, repeatdelay=1000, repeatinterval=500)b.pack()mainloop()
效果:当用户按在按钮上面不动时,经过repeatdelay毫秒(1秒)后,按钮的command每间隔repeatinterval毫秒(0.5秒)就执行一次。
禁用按钮
所有组件都有state参数,表示组件的状态。一共有三个状态:normal, disabled, active。默认的state是normal,此时用户可以点击按钮。而处于disabled禁用的按钮,用户将无法点击。active则是激活状态。
Button(root, text="按钮", state="disabled")
处于禁用状态的按钮,无法点击:
处于激活状态的按钮如果不进行设置是看不出来的,但是设置了activebackground和activeforeground,就可以看出按钮处于激活状态。但是点击松开之后,激活状态就会取消(变成normal状态)。
根据这个原理,我们可以做出点击一次就禁用的按钮:
from tkinter import *root = Tk()def disable():button.config(state="disabled")button = Button(root, text="点击禁用", command=disable)button.pack()mainloop()
如果你忘了config的用法,这里再强调一次:用于改变组件原本设定的参数,这个方法非常常用。
运行后点击一次按钮,按钮的状态由normal改为disabled,无法点击第二次。
点击后>>>
2.3 布局管理(pack,grid,place)
上面已经提过,组件需要在创建后,摆放到屏幕上。一共有三种摆放的方式:pack, grid, place。注意:在同一个容器中,只能使用一种布局方式,要么组件都用pack,要么都用grid,要么都用place。接下来介绍一下这三个方法的参数:
pack
pack适用于简单的布局。
- side:组件靠哪个方向排放,可以是”top”, “bottom”, “left”, “right”,分别是上下左右,默认是”top”。
- anchor:当排放组件的可用空间要多于所需空间时,组件靠哪个方向排放,可选项是八个方位和中心(n, s, w, e, nw, ne, sw, se, center)。默认是”nw”。
from tkinter import *root = Tk()root.geometry("200x200")w1 = Button(root, text="多余空间靠左")w1.pack(anchor="w")root.mainloop()
- expand:组件适应窗口。如设置为True,当窗口中有别的可用空间时,将会自动把组件居中摆放,并且拖拽后仍然适应窗口大小。默认为False。
from tkinter import *root = Tk()root.geometry("200x80")w1 = Button(root, text="W1")w1.pack(expand=True)w2 = Button(root, text="W2")w2.pack(expand=True)root.mainloop()
拖拽窗口后>>>
如果不设置expand,则变成这样,组件不会自动适应窗口大小:
拖拽窗口后>>>
- fill:组件的填充,可选项有”x”, “y”, “both”, “none”,默认为”none”。分别表示:x方向填充,y方向填充,两个方向都填充,无填充。这将根据参数指定填充组件可用空间,一般和expand一起使用,以保证可用空间足够。(下面都设置了expand=True,窗口未拖拽时尺寸200×80)
参数设置 | 未拖拽时效果 | 拖拽后效果 |
fill=”x” | ||
fill=”y” | ||
fill=”both” | ||
fill=”none” |
- padx和pady:分别表示组件与外部容器在x轴和y轴的间隔。可以只提供一个数字,表示左右间隔或上下间隔,也可以提供一个两个项的元组表示左右间隔或上下间隔。不一定要一起设置。
from tkinter import *root = Tk()w1 = Button(root, text="Hello")w1.pack(padx=50, pady=30) #左右间隔50,上下间隔30root.mainloop()
from tkinter import *root = Tk()w1 = Button(root, text="Hello")w1.pack(padx=(50, 40), pady=(30, 60)) #左间隔50,右间隔40,上间隔30,下间隔60root.mainloop()
- ipadx和ipady:表示内部与组件边框的间隔,与padx,pady使用方法类似。
from tkinter import *root = Tk()root.geometry("400x200")w1 = Button(root, text="Hello")w1.pack(ipadx=40, ipady=40)root.mainloop()
grid
pack布局方式适合于简单的布局,在同一容器内,只能进行上下左右四方向的布局。而grid可以实现网格布局,根据行和列指定组件的位置。grid布局和pack一样都支持padx, pady, ipadx, ipady这几个参数。
- row和column:分别指定组件排列的行和列,row和column指定以0为起点,第一行就是row=0。
from tkinter import *root = Tk()root.geometry("400x200")Button(root, text="0行0列").grid(row=0, column=0)Button(root, text="1行0列").grid(row=1, column=0)Button(root, text="0行1列").grid(row=0, column=1)Button(root, text="1行1列").grid(row=1, column=1)root.mainloop()
可以看见,实现了整齐的布局。如果此时把column设置为一个很大的数字,比如999,但是容器上的组件只有1列,那么并不会把组件排放到999列,而是排放在2列的位置。
- rowspan和columnspan:rowspan表示组件占几行的大小,columnspan表示组件占几列的大小。
from tkinter import *root = Tk()root.geometry("400x200")Button(root, text="0行0列").grid(row=0, column=0)Button(root, text="1行0列").grid(row=1, column=0)Button(root, text="0行1列(占两行)").grid(row=0, column=1, rowspan=2)root.mainloop()
from tkinter import *root = Tk()root.geometry("400x200")Button(root, text="0行0列").grid(row=0, column=0)Button(root, text="0行1列").grid(row=0, column=1)Button(root, text="1行0列(占两列)").grid(row=1, column=0, columnspan=2)root.mainloop()
如果在第二段代码的基础上,不加columnspan的设置,结果就会变成这样:
- sticky:在pack布局中和anchor类似,表示组件的方位,同时也可以达到fill的功能。可以提供八个方位+center,来指定组件在可用空间中的排列位置。
from tkinter import *root = Tk()root.geometry("400x200")Button(root, text="Helloooo").grid(row=0, column=0)Button(root, text="Hiiiiii").grid(row=0, column=1)Button(root, text="Hello").grid(row=1, column=0, sticky="e")root.mainloop()
也可以设置组件填充排放。x轴填充表示为”ew”,y轴填充表示”ns”,xy轴both填充设置为”nwse”
也可以设置填充时,同时靠某个方向排放。需要提供三个参数。比如x方向填充时同时靠北(n)排放,可以设置为sticky=”ewn”。
place
place布局适用于更加复杂的,需要准确摆放组件的容器。这种布局不是很常用,因为使用比较麻烦,需要提供xy坐标以及组件的width和height(以像素为单位),place布局不支持padx……几个参数。
- x和y:组件在x轴和y轴上的位置,单位为像素。如果不清楚tkinter坐标系,可以翻回去看一下,左上角为(0, 0)。
- anchor:组件的锚选项,可选有八个方位,以及center。意思是:组件anchor位置的坐标设置为x,y。如:anchor=”n”的时候,如果x=100, y=100,那么组件的最上面的中间的那个点的位置就是(100, 100)。下面是几个例子:
- width和height:指定组件排放时的宽和高。
- relx和rely:组件在x轴或y轴相对于整个容器的位置,是一个0-1之间的浮点数,表示组件位于整个容器的位置。比如想要把组件的x设在容器30%的位置,则可以把位置设为0.3.如果想要把组件居中,就设置relx=0.5, rely=0.5, anchor=”center”。
- relwidth和relheight:指定组件相对于容器的宽与高,是一个0-1之间的浮点数。组件宽是容器宽的50%,则可以把relwidth设置为0.5.
更改组件映射
规范地说,将组件排放布局到容器上,这个过程叫做映射(map)。widget.pack/grid/place()是映射一个组件,自然也有取消映射(Unmap)的方法。
这个方法是布局方法后面加上_forget,pack布局取消映射方法是pack_forget,grid则是grid_forget,place是place_forget。
调用后,相当于隐藏了这个组件。如果还想映射的话,再次调用一下pack/grid/place即可。
将组件布局,也有办法更改布局给的参数。pack_configure, grid_configure, place_configure,可以更改布局时给定的参数。
布局管理
综合使用布局管理,可以美化界面。布局的时候,组件和组件最好都空开一定距离,这样更加美观(pack和grid可以设置padx和pady)。不要把组件挤在一处,不要让窗口长宽比过大。同样,也不要让窗口大小超出屏幕大小,影响用户操作。
同一容器中只能使用一种布局方式,这就带来了一定麻烦和局限性。所以接下来将介绍Frame组件,使用它可以使布局管理更加方便。
2.4 Frame
Frame是框架的意思,让你在容器中能够创建一个子容器。使用Frame,可以对组件编组,也可以使你能够在一个窗口中综合使用不同的布局方式。比如,在窗口中使用pack布局,在窗口上的Frame中使用grid布局,这是允许的。
参考资料:Python Tkinter 框架控件(Frame) | 菜鸟教程
Frame(master=None, cnf={}, **kw)
Frame没有别的参数,只有几个基本参数,如relief, cursor, highlightcolor等。使用也很简单。
创建Frame
from tkinter import *root = Tk()root.geometry("200x200")fr = Frame(root)fr.pack()Button(fr, text="button in frame").pack()Button(fr, text="button2 in frame").pack()mainloop()
看上去,和没有Frame也没有什么区别,我们可以给Frame加上边框(设置relief参数)。注意,设置Frame的时候必须要更改它的边框宽度,即bd(borderwidth)。
fr = Frame(root, relief="solid", bd=2)
Frame的作用
Frame中可以添加容器中能添加的任何组件,甚至可以嵌套Frame。那么,使用Frame的意义是什么呢?可以参考下面几个作用:
- 方便组件的排放:如果想要在窗口顶部横向摆放几个组件,在下面再摆一个组件,使用grid固然可以,但就比较麻烦,行列不容易调整。这时候可以加上Frame,在窗口中摆一个Frame,下面摆一个组件。再在Frame中横向摆三个组件。这样使用pack布局就能轻松完成。
from tkinter import *root = Tk()root.geometry("200x200")fr = Frame(root)fr.pack(padx=5, pady=5)Button(fr, text="1").pack(side="left")Button(fr, text="2").pack(side="left")Button(fr, text="3").pack(side="left")Button(root, text="OK").pack(pady=5)mainloop()
- 方便取消映射或销毁组件:如果一个窗口中插入了大量组件,想要把其中一部分隐藏,就需要调用很多个forget,显得很麻烦。但如果把这些需要隐藏的组件放进一个Frame,到时候只需要隐藏这个Frame,就可以把所有的组件一起隐藏掉了。
from tkinter import *root = Tk()root.geometry("200x200")fr = Frame(root)fr.pack(padx=5, pady=5)Button(fr, text="1").pack(side="left")Button(fr, text="2").pack(side="left")Button(fr, text="3").pack(side="left")Button(root, text="隐藏", command=fr.pack_forget).pack(pady=5)mainloop()
点击隐藏按钮>>>
再比如,想要删除一部分组件,然后换成另外一部分组件,也可以使用Frame。只需要把这些组件放进一个Frame,然后遍历Frame的子组件,对组件挨个销毁即可。
容器的winfo_children方法返回一个列表,包含了容器所有的子组件。destroy方法销毁一个组件,组件方法介绍时提到过。
for widget in frame.winfo_children():widget.destroy() #逐个销毁frame的子组件
这样可以销毁Frame中的所有组件。
2.5 LabelFrame
这个组件与Frame类似,但是可以在左上方显示一段文本或是一个组件。
LabelFrame(master=None, cnf={}, **kw)
参数名称 | 作用 |
text | 显示的文本 |
font | 文本的字体 |
labelanchor | 文本位于Frame的方位 |
labelwidget | 用一个组件替代显示的文本 |
创建LabelFrame
from tkinter import *root = Tk()root.geometry("200x200")fr = LabelFrame(root, text="LabelFrame")fr.pack(fill="both", padx=4)Button(fr, text="1").pack()Button(fr, text="2").pack()mainloop()
labelanchor参数
labelanchor参数设置文本的位置,可选有八个方位,但不包括center,默认是nw。下面是两个示例。
labelwidget参数
如果你不想要LabelFrame的上面显示一段文字,也可以把它替换为别的组件,比如Button。这个组件的master不影响,只要在同一父容器中就行。
from tkinter import *root = Tk()root.geometry("200x200")fr = LabelFrame(root, text="LabelFrame", labelwidget=Button(root, text="按钮"))fr.pack(fill="both", padx=4)Button(fr, text="1").pack()Button(fr, text="2").pack()mainloop()
2.6 Entry
Entry是一个文本框组件,用户可以在里面输入文本。
参考资料:Python —(六)Tkinter窗口组件:Entry_近视的脚踏实地的博客-CSDN博客
Entry(master=None, cnf={}, **kw)
参数名称 | 作用 |
font | 输入文本字体 |
show | 输入文本被显示为什么字符 |
selectbackground | 选中文字的背景色 |
selectforeground | 选中文字颜色 |
insertborderwidth | 光标边框宽度(指定时光标样式为raised) |
insertontime | 光标闪烁时,处于“亮”状态的时长(毫秒) |
insertofftime | 光标闪烁时,处于“灭”状态的时长(毫秒) |
insertwidth | 光标的宽度 |
selectborderwidth | 选中文字的背景边框宽度 |
textvariable | 绑定的StringVar,同步Entry输入的内容 |
readonlybackground | 文本框处于readonly状态下的背景颜色 |
xscrollcommand | x方向滚动条(后面介绍) |
validate | 验证输入合法性的条件 |
vcmd validatecommand | 判断输入合法性的回调函数,或者是一个包含回调和所需参数的元组 |
invcmd invalidcommand | 输入不合法时执行的回调函数 |
常用方法:
方法名称 | 作用 |
get() | 获取文本框的值 |
delete(first, last=None) | 删除文本框中从索引first到last的内容 |
insert(index, s) | 在文本框中插入文本,index是索引,s是插入内容 |
select_range(start, end) | 选中从start到end的文本 |
icursor(index) | 将光标移动到索引处 |
创建Entry
from tkinter import *root = Tk()root.geometry("200x200")ent = Entry(root)ent.pack()mainloop()
出现了一个输入框,可以在里面自由输入内容。
show参数
指定show参数,可以使输入里面的内容显示为一个字符,常用于密码输入。下面的示例,将所有的输入内容显示为*号。
from tkinter import *root = Tk()root.geometry("200x200")Label(root, text="Pwd: ").pack()ent = Entry(root, show="*")ent.pack()mainloop()
无论输入什么内容,都是*号显示。
get方法
get方法获取文本框的值,如下示例:
from tkinter import *root = Tk()root.geometry("200x200")ent = Entry(root)ent.pack()def printget():print(ent.get())Button(root, text="获取输入", command=printget).pack()mainloop()
点击按钮,将会输出文本框中的值。
insert和delete方法
insert方法可以插入一段内容,需要指定一个插入的位置和插入的内容。
插入位置可以是是字符的索引,比如0代表在第一个字符前面插入。也可以是一些特殊的值,比如”end”(在结尾处插入), “insert”(在光标闪烁处插入)
delete方法则用于删除一段内容,需要指定删除的开始和结束位置。位置的设定和insert一样。
tkinter还有一些类也有这两个方法,索引的用法也都基本一样。
readonly状态
Entry可以设置为state=”readonly”,也就是只读状态。处于readonly的Entry不能被输入,但是用户可以选中Entry里面插入的内容,当然也可以复制。如果是disabled状态,用户不但不能输入,而且不能选中里面的内容。
输入验证
validate, validatecommand, invalidcommand这三个参数用于输入验证。输入验证,也就是判断用户在Entry里面输入的内容是否符合要求。
validate参数是输入的条件,也就是在什么情况下,开启输入验证的功能。可以有”focus”, “focusin”, “focusout”, “key”, “all”, “none”这几个可选。
参数值 | 解释 |
focus | 当组件获得或失去焦点时验证 |
focusin | 当组件获得输入焦点(光标闪烁)时验证 |
focusout | 当组件失去输入焦点时验证 |
key | 当输入内容更改时验证,如果验证为False,输入内容不会被插入文本框 |
all | 当上述任何一种情况出现时验证 |
none | 不进行验证(默认值) |
validatecommand是验证的函数,invalidcommand是验证失败时执行的方法。验证条件成立时,会调用validatecommand方法,这个方法要有一个True或False的返回值。如果是True,则验证通过;如果是False,则验证不通过,将会执行invalidcommand方法。
下面是一个示例,只有输入数字才会通过验证。
from tkinter import *root = Tk()root.geometry("200x200")def vld():if e.get().isdigit():print("数字,符合要求")return Trueelse:print("不是数字,不符合要求")return Falsedef wrong():print("输入不符合要求,调用invalidcommand")e = Entry(root, validate="focusout", validatecommand=vld, invalidcommand=wrong)e.pack()mainloop()
运行效果:当输入一个数字,然后把焦点转移(激活另外一个可输入窗口)时,打印“数字,符合要求”。当输入的不是数字时,打印“不是数字,不符合要求”和”输入不符合要求,调用invalidcommand”两句。
validatecommand参数还可以给一个元组(callback, v1, v2, v3, …),包含执行的方法和你希望Entry传递给方法的参数。
参数选项 | 解释 |
%d | 传递一个操作代码:0表示删除操作,1表示插入操作,-1表示textvariable内容被程序更改或组件失去/获得焦点 |
%i | 传递用户插入或删除内容的索引位置,如果是失去/获得焦点或textvariable内容被程序更改,传递-1 |
%P | 传递文本框最新的输入内容 |
%s | 传递调用验证函数前,文本框上一次的输入内容 |
%S | 传递文本框输入或删除的内容 |
%v | 传递当前validate参数的值 |
%V | 传递调用验证函数的原因,是”focusin”(获得焦点), “focusout”(失去焦点), “key”(输入或删除文本框内容), “forced”(textvariable被程序修改) |
%W | 组件名称(Tcl内部名称) |
比如,把validatecommand设为(callback, “%P”, “%s”),那么在调用callback的时候会传递两个参数,一个是文本框最新的输入内容,一个是文本框上一次的输入内容。
需要说明的是,使用validatecommand传递元组的时候,不能直接传递普通的函数,需要注册为Tcl函数才能使用输入验证。注册方法是:
tcl_cmd = root.register(cmd)
下面的一个示例,演示了validatecommand传参。
from tkinter import *root = Tk()root.geometry("200x200")def vld(s):print("输入或删除了", s)return Truevld = root.register(vld) #注册为Tcl函数e = Entry(root, validate="key", validatecommand=(vld, "%S"))e.pack()mainloop()
实例:密码验证系统
下面的一个实例中,用户需输入一串正确的密码,否则无法通过验证。
from tkinter import *correct_pwd = "0123456789" #正确的密码root = Tk()root.title("密码验证系统")def ok():if pwd.get() == correct_pwd:print("验证通过!")root.destroy()else:print("验证失败!")pwd = Entry(root, show="*")pwd.pack()Button(root, text="OK", command=ok).pack()root.mainloop()
2.7 事件绑定
组件的bind方法可以使组件绑定一个事件和一个回调函数,事件有按下某个键,点击组件等,一般是由用户引发的。事件会被传递给回调函数,然后执行函数。
Widget.bind(sequence=None, func=None)
sequence是事件序列,func是检测到事件的回调函数。
参考资料:Tkinter 事件绑定
按键名称
此处只选取一些常用的按键,更多按键名请参考:keysyms manual page – Tk Built-In Commands
按键名(keysym)按键码(keycode)代表的按键Alt_L64左边的Alt按键Alt_R113右边的Alt按键BackSpace22BackSpace(退格)按键Cancel110break按键Caps_Lock66CapsLock(大写字母锁定)按键Control_L37左边的ControlControl_R109右边的ControlDelete107Delete按键Down 104↓按键End 103End按键Escape9 Esc按键Execute 111SysReq按键F167F1按键F268F2按键F369F3按键F470F4按键F571F5按键F672F6按键F773F7按键F874F8按键F975F9按键F10 76F10按键F11 77F11按键F12 96F12按键Home 97Home按键Insert106Insert按键Left 100←按键Linefeed 54Linefeed(Ctrl + J)KP_0 72小键盘数字0KP_1 73小键盘数字1KP_2 74小键盘数字2KP_3 75小键盘数字3KP_4 76小键盘数字4KP_5 77小键盘数字5KP_6 78小键盘数字6KP_7 79小键盘数字7KP_8 80小键盘数字8KP_9 81小键盘数字9KP_Add86小键盘的+按键KP_Begin 84小键盘的中间按键(5)KP_Decimal91小键盘的点按键(.)KP_Delete91小键盘的删除键KP_Divide112小键盘的/按键KP_Down 88小键盘的↓按键KP_End87小键盘的End按键KP_Enter 108小键盘的Enter按键KP_Home 79小键盘的Home按键KP_Insert90小键盘的Insert按键KP_Left 83小键盘的←按键KP_Mutiply63小键盘的*按键KP_Next 89小键盘的PageDown按键KP_Prior 81小键盘的PageUp按键KP_Right 85小键盘的→按键KP_Subtract 82小键盘的-按键KP_Up80小键盘的↑按键Next 105PageDown按键Num_Lock 77NumLock(数字锁定)按键Pause110Pause(暂停)按键Print111PrintScrn(打印屏幕)按键Prior99PageUp按键Return36Enter(回车)按键Right102→按键Scroll_Lock 78ScrollLock按键Shift_L 50左边的Shift按键Shift_R 62右边的Shift按键Tab 23Tab(制表)按键Up98↑按键
sequence
事件序列遵从一定的格式,如果不合理将会报错:
外面由尖括号括起来,中间由-减号隔开。 modifier是条件,表示只有当modifier成立的时候才会执行函数,可以有多个;type是主要事件的类型,当type有detail的时候可以省略type(有时候可能弄混);Detail是事件类型的附带描述,有的事件类型需要Detail,但有的不需要。
这可能有些难懂,下面将详细解析。
type是事件的类型,下面是type的名称:
type | 触发条件 | detail |
Button ButtonPress | 用户点击鼠标 | 1:鼠标左键;2:鼠标中键;3:鼠标右键;4:滑轮向上滚动(Linux);5:滑轮向下滚动(Linux) |
ButtonRelease | 鼠标按键释放 | 1:鼠标左键;2:鼠标中键;3:鼠标右键;4:滑轮向上滚动(Linux);5:滑轮向下滚动(Linux) |
Active | 组件被激活 | |
Deactivate | 组件失去激活 | |
Enter | 光标进入组件范围(不是按下回车键) | |
Leave | 光标离开组件范围 | |
Key KeyPress | 用户按下按键 | 可以指定具体的按键名,参见前面的按键表 |
KeyRelease | 用户释放按键 | 可以指定具体的按键名,参见前面的按键表 |
Map | 组件被映射 | |
Unmap | 组件取消映射 | |
FocusIn | 组件获得焦点 | |
FocusOut | 组件失去焦点 | |
Configure | 组件尺寸被调节或拖拽 | |
Destroy | 组件被销毁 | |
Expose | 窗口或组件的某部分不再被覆盖 | |
Motion | 光标在组件内移动 | |
MouseWheel | 鼠标滚动(Windows和Mac;Linux应为Button-4、5) | |
Visibility | 窗口在屏幕中可见(比如还原最小化、窗口由隐藏变为显示时触发) |
下面是一个示例,演示了bind方法。
from tkinter import *def callback(event):Label(root, text="你点了一下").pack()root = Tk()root.geometry("400x200")root.title("点击窗口")root.bind("", callback)mainloop()
点击几次窗口>>>
事件序列中,Button是type,意思是点击鼠标,后面的1是detail,表示鼠标左键。同样,如果想要用鼠标中键可以改为Button-2,右键可以改为Button-3。如果不指定detail,只是Button,那么三个鼠标键点击都会被检测到。
callback是回调函数,它必须带有一个参数让bind方法传递。当检测到Button-1事件的时候,bind方法会将一个Event对象传递给callback函数的第一个参数,让函数中能够处理检测到事件的信息。下面就来介绍Event对象。
Event
tkinter.Event返回一个Event对象。Event对象在bind绑定时会传递给回调函数。大多数Event是所有事件类型可共用的,但有一些不是。下面是Event的属性。
属性 | 解释 | 仅限事件类型 |
widget | 发生事件的组件 | |
serial | 事件序列号 | |
type | 事件类型 | |
time | 发生事件的时间 | |
x | 鼠标在窗口中的x位置 | |
y | 鼠标在窗口中的y位置 | |
x_root | 鼠标在整个屏幕上的x位置 | Button, ButtonRelease, Key, KeyRelease, Motion |
y_root | 鼠标在整个屏幕上的y位置 | Button, ButtonRelease, Key, KeyRelease, Motion |
num | 鼠标按下的键 | Button, ButtonRelease |
focus | 窗口是否有焦点 | Enter, Leave |
width | 窗口的宽度 | Configure, Expose |
height | 窗口的高度 | Configure, Expose |
keycode | 按键代码(上面的按键表中有) | Key, KeyRelease |
char | 按键的字符 | Key, KeyRelease |
keysym | 按键名称 | Key, KeyRelease |
keysym_num | 按键名称的数字形式 | Key, KeyRelease |
state | 事件状态(数字) | Button, ButtonRelease,Key, KeyRelease, Enter, Leave, Motion |
state | 事件状态(字符串) | Visibility |
delta | 滚轮滚动信息 | MouseWheel |
下面是一个用法示例。
from tkinter import *def callback(event):print("事件类型", event.type)print("点击了鼠标键", event.num)print("事件发生在组件", event.widget)root = Tk()root.geometry("400x200")root.title("点击窗口")root.bind("
运行后点击几次窗口:
Modefier可以指定一些条件,条件成立的时候,才会捕获事件。
Modefier可以是以下内容:
modefier | 成立条件 |
Alt | 用户按着Alt键 |
Control | 用户按着Ctrl键 |
Shift | 用户按着Shift键 |
Button1 B1 | 用户按着鼠标左键 |
Button2 B2 | 用户按着鼠标中键 |
Button3 B3 | 用户按着鼠标右键 |
Button4 B4 | 用户将鼠标滚轮向上滚动(Linux) |
Button5 B5 | 用户将鼠标滚轮向下滚动(Linux) |
Double | 后面的事件类型被连续两次触发,常用于:双击鼠标左键() |
Triple | 后面的事件类型被连续三次触发 |
比如,想要达到检测组合键的效果,按下Ctrl+S的时候,执行某些操作,即可表示为和(bind区分大小写,会区分大写字母和小写字母),这里的Control是一个modefier,是可选的,不写也是一个合理的事件。
这个Control要和Key事件类型的Control_L和Control_R区分开来。如果要单独检测一个Ctrl键,就必须要用Control_L和Control_R,而不能使用条件,因为事件类型才是必选的。
再比如,检测双击鼠标和三击鼠标,事件分别是和。
bind_all方法
bind_all方法可以在一个窗口中,绑定所有的子组件。比如想要把一个窗口里面的所有组件都绑定,那么就可以用root.bind_all(“”, callback)。这样就不需要很麻烦地一个一个组件地绑定。
如果只是bind_all窗口里面的一个组件,那么整个窗口的组件也会被绑定。
虚拟事件
tkinter中同样可以定义自己的事件,也就是虚拟事件,事件格式略有不同,表示为<>,event是事件名,用两层尖括号括起来。当窗口检测到虚拟事件的时候,会执行绑定的回调函数并传递一个event。
既然是自定义事件,那么触发方式也需要自定。event_generate方法可以向组件发送一个事件。这个事件可以是自定义事件,也可以不是自定义事件,而是
Widget.event_generate(sequence, **kw)
sequence是事件的名称,**kw是一些参数,表示传递的event对象里面的属性,比如可以设置x=1,y=1等。不过这些参数和event的属性略有不同,只有部分和Event的属性一样的才会能够调用,所以建议你尽量不要设置**kw,以免出现错误。
下面就来自定义一个事件,这个自定义事件如果bind没有收到,不会报错。
from tkinter import *def generate_click_event():root.event_generate("<>")root = Tk()root.geometry("200x200")Button(root, text="点击", command=generate_click_event).pack()root.bind("<>", lambda event:print("点了一下"))mainloop()
点击按钮>>>
首先,窗口绑定了一个<>事件,双尖括号代表这是一个自定义的事件。当这个事件被捕获,执行后面的lambda event:print(“点了一下”)。然后,当我们点击按钮的时候,会执行command(),root组件上生成了一个事件<>,这个事件随即被bind捕获,然后调用后面的print(“点了一下”)。
注:如果不了解匿名函数lambda的用法,请参考https://blog.csdn.net/PY0312/article/details/88956795
bindtags方法
事件处理有先后顺序,这些顺序可以获取或进行修改。组件的bindtags方法会返回一个列表,包含事件处理的顺序。
Widget.bindtags(tagList=None)
以Button为例,一个没有任何别的设置的Button会返回这样一个元组:
('.!button', 'Button', '.', 'all')
这个元组中,第一个是这个Button的Tcl内部名称,代表button.bind绑定的事件。第二个是组件的名称,代表这个Button从定义起就绑定的事件,比如点击时会执行command,点击时按钮会下陷。第三个是Button的父容器的Tcl内部名称,代表父容器绑定的事件。最后一个是窗口中bind_all的绑定事件。
如果给定bindtags方法tagList参数,可以改变事件的执行顺序,这个tagList形式仿照bindtags返回的形式。比如只需要执行绑定的事件,而不需要执行其他事件,那么就设为button.bindtags(“.!button”)。
注:不要认为所有的Button组件的Tcl内部名称都是.!button。设置组件的name参数可以设定组件的名称。而调用组件时写作的内部名称比较复杂,是父容器+组件名称这种写法。可以通过str(widget)返回组件的内部名称。
通过这样,我们还可以将其他组件的bind方法作用到这个组件上。
from tkinter import *root = Tk()b1 = Button(root, text="b1", command=lambda:print("command b1"))b1.pack()b1.bind("", lambda x:print("bind b1"))b2 = Button(root, text="b2", command=lambda:print("command b2"))b2.pack()b2.bind("", lambda x:print("bind b2"))b1.bindtags((str(b1), str(b2), "Button"))mainloop()
设置b1的bindtags中包含了b1的bind事件和b2的bind事件。点击b1按钮的时候,不仅会执行b1.bind的回调函数,也会执行b2.bind的回调函数,打印:bind b1和bind b2.
如果想要在一个事件执行之后,不再执行后续的事件,可以在这个事件触发的回调函数中返回”break”字符串。这被解释器捕获后,将不会执行后面的事件。
from tkinter import *root = Tk()e = Entry()e.pack()def rb(x):print("rb")return "break" #不执行后续事件e.bind("", rb)root.bind("", lambda x:print("root bind"))mainloop()
如上面这段代码,在Entry中输入文本,会打印”rb”,但是输入的内容不会插入到Entry中,也不会打印root bind。由于Entry组件默认的bindtags顺序是Entry.bind在前,Entry的默认定义事件和父容器的定义在后。所以当Entry.bind的回调函数返回了”break”,后面的事件都不会再执行了。
unbind方法
unbind方法将某个事件绑定解除。
Widget.unbind(sequence)
from tkinter import *root = Tk()lab = Label(root, text="Label")lab.pack()lab.bind("", lambda x:print("Hello"))lab.unbind("")mainloop()
这段代码给label绑定了一个事件,但接着又解除绑定了,所以点击后组件不会有反应。
关于事件绑定的实例
下面提供3个示例,帮助大家更好地了解事件的用法。
- 示例1:
from tkinter import *root = Tk()root.title("获取鼠标位置")root.geometry("200x200")def change_label(event):mouse_pos = (event.x_root, event.y_root) #鼠标在屏幕上的位置lab.place(anchor="nw", x=event.x, y=event.y) #调整lab的位置lab.config(text=str(mouse_pos)) #更改label的text为鼠标位置lab = Label(root, text="请移动你的鼠标")lab.place(anchor="center", x=100, y=100)root.bind("", change_label)mainloop()
运行效果:鼠标在窗口上滑动的时候,label会显示为鼠标在屏幕上的位置,并且跟随鼠标移动。
- 示例2:
from tkinter import *root = Tk()root.title("获取按键")root.geometry("200x200")def get_key(event):lab.config(text=event.keysym)lab = Label(root, text="请按键")lab.pack()root.bind("", get_key)mainloop()
运行效果:在键盘上按下按键,会显示出按键的名称。
- 示例3:
from tkinter import *root = Tk()root.title("连续事件检测工具")root.geometry("200x200")def get_key(event, num):lab.config(text="按了"+str(num)+"次"+event.keysym)lab = Label(root, text="请按键")lab.pack()root.bind("", lambda event:get_key(event, 1))root.bind("", lambda event:get_key(event, 2))root.bind("", lambda event:get_key(event, 3))mainloop()
运行效果:在键盘上按键或连续按键,会显示出键名和按键数量。
2.8 Variable(tkinter变量)
tkinter中所有的var有StringVar()文本变量对象,IntVar()整数变量对象,DoubleVar()浮点数变量对象,BooleanVar()布尔值变量对象,可以绑定到组件。它们都是继承tkinter.Variable,用法都是一样的。这几个之前略有提及过,下面详细介绍它们。
Variable(master=None, value=None, name=None)
参数 | 作用 |
master | 指定variable的父组件,一般不用这个参数 |
value | 指定variable的值 |
name | 指定variable在Tcl中的名称,一般不用这个参数 |
下面的代码创建一个Variable(以StringVar为例)。
var = StringVar(value="value")
这段代码定义一个StringVar,并设置它的值。
var.get()
这段代码设置var的值。
var.set("value")var.initialize("value") #两种写法都可以
trace_add方法
trace_add方法用于监测variable的变化,后面绑定一个回调函数。当检测到变化发生时,执行后面的函数。
Variable.trace_add(mode, callback)
这种监测有3种模式:write, read, unset。write是指写入,当变量内容出现改动时调用函数,常用于Entry组件中,用户输入追踪。read是指读取,当读取变量信息(var.get())的时候执行函数。unset是指变量删除,当执行Variable的__del__方法,或是del var的时候执行函数。
下面就让我们根据这个原理做一个文本框,在里面输入的时候窗口标题同步改动:
from tkinter import *root = Tk()root.geometry("200x200")var = StringVar()var.trace_add("write", lambda *args:root.title(var.get()))e = Entry(root, textvariable=var)e.pack()mainloop()
用户在Entry里面输入的时候,textvariable的值会被设为用户输入的内容。运行效果如下:
输入>>>
那么定义回调的函数时为什么要提供一个*args呢?这是因为trace_add会向函数传递3个参数,马上就会介绍到这3个参数。
下一个示例,演示了r模式。用户点击读取按钮,会输出读取内容和“读取成功”的提示。
from tkinter import *root = Tk()root.geometry("200x200")var = StringVar()var.trace_add("read", lambda *args:print("读取成功"))e = Entry(root, textvariable=var)e.pack()b = Button(root, text="Read", command=lambda:print(var.get()))b.pack()mainloop()
可以看到,执行var.get()的时候就打印了读取成功。
注:刚开始还多打印了一次读取成功,是因为Entry组件在指定textvariable的时候,会读取var的值,然后把Entry内容设置为这个值,导致trace被触发。如果想要避免这个问题,把trace_add放到Entry定义后面即可。
那么再试一下unset模式:
from tkinter import *root = Tk()root.geometry("200x200")var = StringVar()var.trace_add("unset", lambda *args:print("变量被unset"))e = Entry(root, textvariable=var)e.pack()b = Button(root, text="Del", command=var.__del__)b.pack()mainloop()
如果需要在var被读取或写入的时候执行函数,也可以把mode参数设定为一个元组,如(“write”, “read”)
variable也有一个方法叫做trace_variable,也作trace。有很多教程讲解的是这个方法。tk文档指出:这个方法使用了现已弃用的Tcl/Tk方法,有可能后期会移除,所以建议不要使用这个方法,而是使用trace_add。
trace传递给回调函数的参数
trace_add方法一共会传递3个参数。第一个参数是var的名称(name),可以在variable的参数里面设置,第二个参数是列表的索引(如果var是列表),第三个参数是设置的trace的模式。这三个参数中,只有第三个可能有点用处。
trace_remove方法
如果想要删除追踪,可以使用trace_remove方法。
Widget.trace_remove(mode, cbname)
mode是模式,也就是要删除的trace的模式。上面讲到的trace_add方法会返回一个标识,就是cbname。比如定义了一个追踪:cbn = widget.trace(“write”, callback),后期想要删除这个trace,就要调用widget.trace_remove(“write”, cbn)。
trace_info方法
trace_info方法返回该variable所有的追踪信息。由于比较少用,就不多做解释了。
2.9 Toplevel
Toplevel可以用来创建一个新的窗口,它必须继承一个窗口,可以是Tk,也可以是Toplevel。
Toplevel(master=None)
创建Toplevel
from tkinter import *root = Tk()top = Toplevel(root)mainloop()
运行后,可以看到两个窗口,一个是Tk,一个是Toplevel。当Tk关闭时,Toplevel也会关闭。
Toplevel的父窗口也是Toplevel,这也是支持的。另外,Toplevel方法和Tk方法一样。
但是Toplevel并不需要调用mainloop,只需要调用根窗口的mainloop方法即可。
下一篇:Python tkinter(GUI编程)模块全解(中)_Python zzy的博客-CSDN博客
如果你在开发Python tkinter程序时遇到了问题,或是有文章内容的建议,可以私信联系我,感谢支持!