提示:下滑文章左侧可以查看目录!

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状态下的背景颜色
xscrollcommandx方向滚动条(后面介绍)
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方法可以向组件发送一个事件。这个事件可以是自定义事件,也可以不是自定义事件,而是