总目录:tkinter/README.md · Python-ZZY/CSDN-articles - Gitee.com

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 tk
from tkinter import *

如果导入时候就出现了错误,提示找不到_tkinter这一模块,或者调用里面的方法时出现版本错误提示,可能是因为安装时不到位,没有勾选Tk/Tcl这一选项。在安装包中选择Modify,更改Python的安装即可

接下来让我们了解一下自己tkinter的版本:

import tkinter
print(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的设定,这个窗口无法改变大小,只能保持在500x500。 

下面着重讲一下geometry方法。这个方法不仅可以设置窗口的尺寸,也可以设置窗口在电脑屏幕上的位置。给定参数的格式是:widthxheight+x+y。root.geometry("300x100+20+50")代表的就是把root窗口设置为300x100的尺寸,与屏幕最左边相隔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) #只设置大小20
lab = 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。注意:直接在图片文件后面改后缀不会改变图片本身的文件类型!更需要强调的一点是,要保证储存PhotoImage图片对象变量不会被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, ImageTk

root = 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,窗口未拖拽时尺寸200x80)
参数设置未拖拽时效果拖拽后效果
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,上下间隔30

root.mainloop()

 

from tkinter import *

root = Tk()

w1 = Button(root, text="Hello")
w1.pack(padx=(50, 40), pady=(30, 60)) #左间隔50,右间隔40,上间隔30,下间隔60

root.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 True
    else:
        print("不是数字,不符合要求")
        return False

def 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 True

vld = 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_L                64                左边的Alt按键
Alt_R                113               右边的Alt按键
BackSpace            22                BackSpace(退格)按键
Cancel               110               break按键
Caps_Lock            66                CapsLock(大写字母锁定)按键
Control_L            37                左边的Control
Control_R            109               右边的Control
Delete               107               Delete按键
Down                 104               ↓按键
End                  103               End按键
Escape               9                 Esc按键
Execute              111               SysReq按键
F1                   67                F1按键
F2                   68                F2按键
F3                   69                F3按键
F4                   70                F4按键
F5                   71                F5按键
F6                   72                F6按键
F7                   73                F7按键
F8                   74                F8按键
F9                   75                F9按键
F10                  76                F10按键
F11                  77                F11按键
F12                  96                F12按键
Home                 97                Home按键
Insert               106               Insert按键
Left                 100               ←按键
Linefeed             54                Linefeed(Ctrl + J)
KP_0                 72                小键盘数字0
KP_1                 73                小键盘数字1
KP_2                 74                小键盘数字2
KP_3                 75                小键盘数字3
KP_4                 76                小键盘数字4
KP_5                 77                小键盘数字5
KP_6                 78                小键盘数字6
KP_7                 79                小键盘数字7
KP_8                 80                小键盘数字8
KP_9                 81                小键盘数字9
KP_Add               86                小键盘的+按键
KP_Begin             84                小键盘的中间按键(5)
KP_Decimal           91                小键盘的点按键(.)
KP_Delete            91                小键盘的删除键
KP_Divide            112               小键盘的/按键
KP_Down              88                小键盘的↓按键
KP_End               87                小键盘的End按键
KP_Enter             108               小键盘的Enter按键
KP_Home              79                小键盘的Home按键
KP_Insert            90                小键盘的Insert按键
KP_Left              83                小键盘的←按键
KP_Mutiply           63                小键盘的*按键
KP_Next              89                小键盘的PageDown按键
KP_Prior             81                小键盘的PageUp按键
KP_Right             85                小键盘的→按键
KP_Subtract          82                小键盘的-按键
KP_Up                80                小键盘的↑按键
Next                 105               PageDown按键
Num_Lock             77                NumLock(数字锁定)按键
Pause                110               Pause(暂停)按键
Print                111               PrintScrn(打印屏幕)按键
Prior                99                PageUp按键
Return               36                Enter(回车)按键
Right                102               →按键
Scroll_Lock          78                ScrollLock按键
Shift_L              50                左边的Shift按键
Shift_R              62                右边的Shift按键 
Tab                  23                Tab(制表)按键
Up                   98                ↑按键

sequence

事件序列遵从一定的格式,如果不合理将会报错:

<Modifier...-Type-Detail>

外面由<>尖括号括起来,中间由-减号隔开。 modifier是条件,表示只有当modifier成立的时候才会执行函数,可以有多个;type是主要事件的类型,当type有detail的时候可以省略type(有时候可能弄混);Detail是事件类型的附带描述,有的事件类型需要Detail,但有的不需要。

这可能有些难懂,下面将详细解析。

<Type-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("<Button-1>", 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("<Button>", callback)

mainloop()

 运行后点击几次窗口:

<Modifier-Type-Detail>

Modefier可以指定一些条件,条件成立的时候,才会捕获事件。

Modefier可以是以下内容:

modefier成立条件
Alt用户按着Alt键
Control用户按着Ctrl键
Shift用户按着Shift键

Button1

B1

用户按着鼠标左键

Button2

B2

用户按着鼠标中键

Button3

B3

用户按着鼠标右键

Button4

B4

用户将鼠标滚轮向上滚动(Linux)

Button5

B5

用户将鼠标滚轮向下滚动(Linux)
Double后面的事件类型被连续两次触发,常用于:双击鼠标左键(<Double-Button-1>)
Triple后面的事件类型被连续三次触发

比如,想要达到检测组合键的效果,按下Ctrl+S的时候,执行某些操作,即可表示为<Control-S>和<Control-s>(bind区分大小写,会区分大写字母和小写字母),这里的Control是一个modefier,是可选的,不写也是一个合理的事件。

这个Control要和Key事件类型的Control_L和Control_R区分开来。如果要单独检测一个Ctrl键,就必须要用Control_L和Control_R,而不能使用条件,因为事件类型才是必选的。

再比如,检测双击鼠标和三击鼠标,事件分别是<Double-Button>和<Triple-Button>。

bind_all方法

bind_all方法可以在一个窗口中,绑定所有的子组件。比如想要把一个窗口里面的所有组件都绑定<Button-1>,那么就可以用root.bind_all("<Button-1>", callback)。这样就不需要很麻烦地一个一个组件地绑定。

如果只是bind_all窗口里面的一个组件,那么整个窗口的组件也会被绑定。

虚拟事件

tkinter中同样可以定义自己的事件,也就是虚拟事件,事件格式略有不同,表示为<<event>>,event是事件名,用两层尖括号括起来。当窗口检测到虚拟事件的时候,会执行绑定的回调函数并传递一个event。

既然是自定义事件,那么触发方式也需要自定。event_generate方法可以向组件发送一个事件。这个事件可以是自定义事件,也可以不是自定义事件,而是<Button>那种自带的事件类型。

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("<<MyClickEvent>>")

root = Tk()
root.geometry("200x200")

Button(root, text="点击", command=generate_click_event).pack()
root.bind("<<MyClickEvent>>", lambda event:print("点了一下"))

mainloop()

 点击按钮>>>

首先,窗口绑定了一个<<MyClickEvent>>事件,双尖括号代表这是一个自定义的事件。当这个事件被捕获,执行后面的lambda event:print("点了一下")。然后,当我们点击按钮的时候,会执行command(),root组件上生成了一个事件<<MyClickEvent>>,这个事件随即被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("<Button-1>", lambda x:print("bind b1"))

b2 = Button(root, text="b2", command=lambda:print("command b2"))
b2.pack()
b2.bind("<Button-1>", 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("<Key>", rb)
root.bind("<Key>", 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("<Button-1>", lambda x:print("Hello"))
lab.unbind("<Button-1>")

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("<Motion>", 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("<Key>", 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("<Key>", lambda event:get_key(event, 1))
root.bind("<Double-Key>", lambda event:get_key(event, 2))
root.bind("<Triple-Key>", 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程序时遇到了问题,或是有文章内容的建议,可以私信联系我,感谢支持!

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐