先载入一些必备包:
from tkinter import ttk, Tk
from tkinter import N, W, E, S
from tkinter import HORIZONTAL, VERTICAL
from tkinter import StringVar
1 标签:ttk.Label
标签是显示文本或图像的小部件,用户通常只能查看文本或图像,但不能与之交互。标签用于识别控件或用户界面的其他部分、提供文本反馈或结果等。一般迭的使用方法:
label = ttk.Label(parent, text='')
下面详细介绍其主要参数,在 tk/tcl 中被称为配置选项(configuration option):
1.1 展示文本
在窗口显示的信息是由 text
设定的,对于用户来说,是不可更改的,但对于开发者来说,它是可以改变的。
root = Tk() # 主窗口
# 设置标签
label = ttk.Label(root, text='您好')
label.grid() # 在 root 布局
可以通过索引的方式获取文本信息:
label['text']
显示结果为:
'您好'
也可以通过 configure
函数获取文本信息:
label.configure('text')
显示结果为:
('text', 'text', 'Text', '', '您好')
由于有两种获取文本信息的方式,所以,对应的也有两种修改文本信息的方式:
- 索引赋值
label['text'] = '早上好'
- 修改
configure
的text
参数的值:
label.configure(text='早上好')
1.2 监视标签
可以使用 "textvariable"
选项监视您的代码的变量,同时也支持修改变量取值。
res = StringVar() # 用于监视的变量
label['textvariable'] = res
使用 set
方法可以修改监视变量:
res.set('再见')
此时 label['text']
的取值也被改变:
label['text']
显示结果为:
'再见'
使用get
方法可以获取监视变量的取值:
res.get()
显示结果为:
'再见'
1.3 显示图片
展示图片需要两步,首先使用 PhotoImage
载入图片,最后使用 ttk.Label
的 'image'
选项。
from tkinter import PhotoImage
# 先载入图片
img = PhotoImage(file='images/image.gif')
label['image'] = img # 设置配置选项
可以设置 'compound'
选项用于设定图片与文本的混搭显示布局。取值的含义分别为 'text'
(仅显示文本),'image'
(仅显示图片),'center'
(文本在图片中央),'top'
(图片在文本上方),'left'
,'bottom'
和 'right'
。比如:
label['compound'] = 'top'
1.4 布局设置
虽然标签的整体布局(即标签位于用户界面中的位置和大小)由几何管理器确定,但几个特殊的选项可以帮助您更好地控制标签在几何管理器提供的框中的显示方式。
如果提供给标签的框大于标签对其内容的要求,则可以使用 "anchor"
选项指定标签应附加到哪个边或角,这将在相反的边缘或角中留下任何空白。可能的值被指定为指南针方向:"n"
(北边或上边缘)、"ne"
(东北或右上角)、"e"
、"s"
,"sw"
,"w"
,"nw"
或 "center"
。
您还可以使用 "justify"
选项控制文本的对齐方式,该选项可以具有值 "left", "center" or "right"。如果只有一行文本,这与使用 "anchor"
选项几乎相同,但多行文本更有用。
比如,设置:
label['anchor'] = 'sw'
1.5 字体、颜色等
带有 "style"
选项的小部件可用来修改字体、颜色等。您可以使用 "font"
配置选项指定用于显示标签文本的字体。
前景(文本)和背景颜色也可以通过 "foreground"
和 "background"
选项进行更改。
label['font'] = 'Arial 20'
label["foreground"] = 'red'
label["background"] = 'lightblue'
# 显示窗口
root.mainloop()
显示结果为:
2 文本输入框:ttk.Entry
Entry(文本输入框)为用户提供了一个单行文本字段,他们可以用来输入字符串值。 这些几乎可以是任何东西:名字,城市,密码,社会保险号等等。
使用 ttk.Entry
函数创建 Entry。可以指定 "width"
配置选项,以提供 Entry 的宽的字符数。例如,允许您为邮政编码提供一个较短的 Entry。
通常使用 "textvariable"
配置选项指定的链接变量来访问 Entry 的值。 请注意,与各种按钮不同,Entry 旁边没有单独的文本或图像来标识它们。 为此使用单独的标签小部件。
您也可以直接获取或更改 Entry 小部件的值,而无需通过链接变量。 "get"
方法返回当前值,"delete"
和 "insert"
(对应插入光标的当前位置),"end"
(对应已存在文本的后一个位置),"anchor"
(如果有的话,对应第一个被选中的字符)方法可让您更改内容,例如
print('current value is %s' % name.get())
name.delete(0,'end') # delete between two indices, 0-based
name.insert(0, 'your name') # insert new text at a given index
请注意,Entry小部件没有 "command"
选项,每当 Entry 更改时,该选项都将调用回调。 要监视更改,您应该监视链接变量的更改。
2.1 密码:Passwords
Entry可用于密码,其中实际内容显示为项目符号或其他符号。 为此,将 "show"
配置选项设置为要显示的字符,例如 “*”。
下面直接看一个例子:
from tkinter import ttk, Tk, StringVar
if __name__ == '__main__':
root = Tk() # 主窗口
root.title('Entry 测试')
root.geometry('400x300') # 设定窗口大小
user_name = StringVar() # 记录用户名称
user_name.set('Tom')
# 设置字符个数不得超过 7
name = ttk.Entry(root, textvariable=user_name, width=7) # root 上的 Entry 小部件
name.pack()
# 隐藏名称
hide_name = ttk.Entry(root, textvariable=user_name, show=' ')
hide_name.pack()
# 隐藏为 *
hide_name1 = ttk.Entry(root, textvariable=user_name, show='*')
hide_name1.pack()
root.mainloop()
显示结果为:
2.2 小部件状态:Widget States
像各种按钮一样,也可以通过 "state"
命令将 Entry 置于禁用状态(并使用 "instate"
查询)。 Entry 也可以使用状态标志 "readonly"
; 如果设置,则用户无法更改 Entry,尽管他们仍然可以选择其中的文本(并将其复制到剪贴板)。 还有一个 "invalid"
状态,如果输入窗口小部件验证失败则设置。(具体说明见按钮:ttk.Button)
2.3 验证输入内容的合法性的
我们需要利用 ttk.Entry
的 'validate'
与 'validatecommand'
选项,检查输入的文本是否合法,具体的步骤是:
- 定义一个负责检查输入内容的回调函数,如果合法则返回
True
,否则返回False
; - 使用
ttk.Entry
的方法register
将回调函数封装为 Tcl,它会返回一个字符串,用它来设定'validatecommand'
选项; - 设置
'validate'
,声明调用回调函数的时机,常用的选项有:-
'focus'
:输入框获取或者失去焦点时 -
'focusin'
:输入框获取焦点时 -
'focusin'
:输入框失去焦点时 -
'key'
:内容改变时 -
'all'
:以上任何情况发生时 -
'none'
:关闭内容检查,这是默认值
-
具体的选项描述见下表:
选项 | 描述 |
---|---|
'validate' | 该选项设置是否启用内容验证 |
'invalidcommand' | 1. 指定当输入框输入的内容“非法”时调用的函数;2. 也就是指定当 'validatecommand' 选项指定的函数返回 False 时的函数 |
'validatecommand' | 1. 该选项指定一个验证函数,用于验证输入框内容是否合法;2. 验证函数需要返回 True 或 False 表示验证结果;3. 注意,该选项只有当 'validate' 的值非 "none" 时才有效 |
比如,下面的代码验证输入内容是否为 "Python":
class Window(Tk):
def __init__(self):
super().__init__()
self.in_var = StringVar() # 输入变量
self.out_var = StringVar() # 输出变量
self.input_entry = ttk.Entry(textvariable=self.in_var)
self.input_entry['validate'] = "focusout"
self.input_entry['validatecommand'] = self.test
# 测试 self.input_entry 光标 离开之后的验证
self.show_entry = ttk.Entry(textvariable=self.out_var)
self._layout()
def test(self):
'''验证输入内容是否为 Python'''
if self.input_entry.get() == 'Python':
self.out_var.set('输入正确')
return True
else:
self.out_var.set('输入错误')
return False
def _layout(self):
self.input_entry.grid()
self.show_entry.grid()
window = Window()
window.mainloop()
添加如下两个设置:
self.input_entry['invalidcommand'] = self.test2
def test2(self):
self.show_entry.insert('end', " 我被调用了......")
return True
便可在验证 'invalidcommand'
的使用情况。
2.4 使用注册机制验证输入内容的合法性的
我们也可以使用注册机制验证输入内容的合法性的。即使用配置选项 validatecommand=(register_func, s1, s2, ...)
,其中 register_func
为验证函数名,s1
、s2
这些是额外的选项,这些选项会作为参数依次传给 register_func
函数。下表列出额外的选项的描述:
额外选项 | 含义 |
---|---|
'%d' | 操作代码:0 表示删除操作;1 表示插入操作;2 表示获得、失去焦点或 'textvariable' 变量的值被修改 |
'%i' | 1. 当用户尝试插入或删除操作的时候,该选线表示插入或删除的位置(索引号);2. 如果是由于获得、失去焦点或 'textvariable' 变量的值被修改而调用验证函数,那么该值是 -1 |
'%P' | 1. 当输入框的值允许改变的时候,该值有效;2. 该值为输入框的最新文本内容 |
'%s' | 该值为调用验证函数前输入框的文本内容 |
'%S' | 1. 当插入或删除操作触发验证函数的时候,该值有效;2. 该选项表示文本被插入和删除的内容 |
'%v' | 该组件当前的 'validate' 选项的值 |
'%V' | 1. 调用验证函数的原因;2. 该值是 'focusin','focusout','key' 或 'forced'('textvariable' 选项指定的变量值被修改)中的一个 |
'%W' | 该组件的名字 |
下面看一个例子:
class Window(Tk):
def __init__(self):
super().__init__()
self.in_var = StringVar() # 输入变量
self.out_var = StringVar() # 输出变量
self.input_entry = ttk.Entry(textvariable=self.in_var)
self.input_entry['validate'] = "focusout"
self.test_cmd= self.register(self.test) # 注册
self.input_entry['validatecommand'] = (self.test_cmd, '%P', '%v', '%W')
# 测试 self.input_entry 光标 离开之后的验证
self.show_label = ttk.Label(textvariable=self.out_var)
self._layout()
def test(self, content, reason, name):
'''验证输入内容是否为 Python'''
if content == 'Python':
str1 = '输入正确\n'
str1 += f"{content}, {reason}, {name}"
self.out_var.set(str1)
return True
else:
str1 = '输入错误\n'
str1 += f"{content}, {reason}, {name}"
self.out_var.set(str1)
return False
def _layout(self):
self.input_entry.grid()
self.show_label.grid()
window = Window()
window.mainloop()
显示效果:
您也可以使用类的重写功能修改验证内容,比如:
class ValidatingEntry(Entry):
# base class for validating entry widgets
def __init__(self, master, value="", **kw):
apply(Entry.__init__, (self, master), kw)
self.__value = value
self.__variable = StringVar()
self.__variable.set(value)
self.__variable.trace("w", self.__callback)
self.config(textvariable=self.__variable)
def __callback(self, *dummy):
value = self.__variable.get()
newvalue = self.validate(value)
if newvalue is None:
self.__variable.set(self.__value)
elif newvalue != value:
self.__value = newvalue
self.__variable.set(self.newvalue)
else:
self.__value = value
def validate(self, value):
# override: return value, new value, or None if invalid
return value
3 按钮:ttk.Button
按钮非常适合用户交互,特别是点击按钮以执行某些操作。与标签一样,它们可以显示文本或图像,但也具有用于控制其行为的一系列新选项。通常它们的内容和命令回调是同时设置的:
button = ttk.Button(parent, text='Okay', command=submitForm)
3.1 文本或图像
按钮同样有的 "text"
, "textvariable"
(很少使用),"image"
和 "compound"
配置选项作为标签,用于控制按钮是否显示文本或图像(同 ttk.Entry)。按钮还有一个 "default"
选项,告诉 Tk 按钮是用户界面中的默认按钮(即,如果用户点击 Enter 或 Return 将调用的按钮)(可以使用键盘的 Tab
进行按钮间的跳转)。将此选项设置为 "active"
以指定这默认按钮为激活状态;常规状态为 "normal"
。请注意,设置此选项不会创建事件绑定,需要使返回或输入键激活按钮。示例如下:
root = Tk()
root.geometry('200x100')
b = ttk.Button(root, text='普通状态')
b.grid()
b1 = ttk.Button(root, text='激活状态')
b1.grid()
b1["default"] = "active"
root.mainloop()
显示结果为:
3.2 按钮状态
按钮和许多其他小部件的状态可以是:正常状态,被按下的状态,被禁用状态(按钮灰显且无法按下,当按钮的命令在给定时间点不适用时,将执行此操作)。
其实,所有主题小部件都带有一个内部状态,这是一系列二进制标志。您可以设置或清除这些不同的标志,以及使用 "state"
和 "instate"
方法检查当前设置。按钮使用 "disabled"
标志来控制用户是否可以按下该按钮。例如:
主题小部件可用的状态标志的列表有:"active", "disabled", "focus", "pressed", "selected", "background", "readonly", "alternate" 和 "invalid"。这些都在主题小部件参考中有描述。
3.3 命令回调
"command"
选项用于在按钮的操作和应用程序之间提供接口。当用户单击按钮时,解释器将调用该选项提供的脚本。直接看一个例子:
root = Tk()
var = StringVar() # 设置文字变量储存器
lb = ttk.Label(root,
textvariable=var, # 使用 textvariable 替换 text, 因为这个可以变化
width=15,
background='green', font='Arial 18')
lb.grid()
on_hit = False # 默认初始状态为 False
def hit_me():
global on_hit
if on_hit: # 从 True 状态变成 False 状态
on_hit = False
var.set('') # 设置 文字为空
else: # 从 False 状态变成 True 状态
on_hit = True
var.set('你打了我') # 设置标签的文字
b = ttk.Button(root, text='打我', command=hit_me)
b.grid()
root.mainloop()
显示结果为:
实现的功能是:当点击按钮时,显示文字 你打了我
,再点一次文字便会消失。
还可以要求按钮从应用程序调用(invoke
)命令回调。这很有用,因此您不需要重复在程序中多次使用 command
,即使按钮的状态发生改变也无需修改代码。可以看一个例子:
class ClickInvoke(Tk):
def __init__(self):
super().__init__()
self.lb_var1 = StringVar()
self.lb_var2 = StringVar()
self.layout()
def layout(self):
self.lb1 = ttk.Label(textvariable=self.lb_var1, widt=15,
background='green', font='Arial 18')
self.lb2 = ttk.Label(textvariable=self.lb_var2, widt=15,
background='red', font='Arial 18')
self.b1 = ttk.Button(text='Button 1', command=self.click1)
self.b2 = ttk.Button(text='Button 2', command=self.click2)
self.b3 = ttk.Button(text='Button 3', command=self.click3)
self.lb1.grid()
self.b1.grid(column=0, row=1, sticky=(W, E))
self.b2.grid(column=1, row=1, sticky=(W, E))
self.b3.grid(column=2, row=1, sticky=(W, E))
self.lb2.grid()
def click1(self):
self.lb_var1.set('Button 1 clicked.')
self.lb_var2.set('')
def click2(self):
self.lb_var2.set('Button 2 clicked.')
self.lb_var1.set('')
def click3(self):
self.b1.invoke() # 调用按钮 `Button 1`
self.lb_var2.set('Button 3 clicked.')
app = ClickInvoke()
app.mainloop()
显示的结果为:
实现的功能是:点击按钮 Button 1
时仅仅显示绿色区域的文字,点击按钮 Button 2
仅仅显示红色区域的文字,点击按钮 Button 3
时,红色区域的文字发生改变,同时也回调了按钮 Button 1
。
4 复选按钮:ttk.Checkbutton
复选按钮类似于常规按钮,不同之处在于用户不仅可以按下它调用命令回调,而且还包含某种二进制值(如 toggle)。当要求用户在两个选项(例如,两个不同的值)之间进行选择时,便可使用复选按钮。
创建方法:
measureSystem = StringVar()
check = ttk.Checkbutton(parent, text='Use Metric',
command=metricChanged,
variable=measureSystem,
onvalue='metric', offvalue='imperial')
复选按钮使用的选项与常规按钮相同: "text", "textvariable","image" 和 "compound" 选项控制标签的显示(复选框本身旁边),并且 "state" 和 "instate" 方法允许您设置 " disabled" 状态标志以启用或禁用复选按钮。 同样, "command" 选项使您可以指定每次用户切换检查按钮时都要调用的脚本, "invoke" 方法也将执行相同的回调。
我们之前已经了解了如何使用 "textvariable" 选项将小部件的标签与程序中的变量相关联。 与之类似,复选按钮使用 "variable"
选项用于读取或更改窗口小部件的当前值,并在切换窗口小部件时进行更新。 默认情况下,选中小部件时,复选按钮使用的值为 “1” ,未选中时使用的值为 “0” ,可以使用 "onvalue"
(选中的取值)和 "offvalue"
(未选中的取值) 选项将其更改为几乎所有值。
看一个例子:
from tkinter import IntVar
class MyLove(Tk):
def __init__(self):
super().__init__()
self.title('爱好') # 设定标题
self.geometry('200x100') # 设定尺寸
self.var1 = IntVar()
self.var2 = IntVar()
self.lb = ttk.Label(background='yellow', width=20, text='')
self.c1 = ttk.Checkbutton(text='Python', variable=self.var1,
command=self.print_love)
self.c2 = ttk.Checkbutton(text='C++', variable=self.var2,
command=self.print_love)
self.lb.grid()
self.c1.grid()
self.c2.grid()
def print_love(self):
if (self.var1.get() == 1) & (self.var2.get() == 0):
self.lb.config(text='仅仅喜欢 Python ')
elif (self.var1.get() == 0) & (self.var2.get() == 1):
self.lb.config(text='仅仅喜欢 C++')
elif (self.var1.get() == 0) & (self.var2.get() == 0):
self.lb['text'] = '都不喜欢'
else:
self.lb['text'] = '都是我的菜'
root = MyLove()
root.mainloop()
显示结果为:
当链接的变量既不包含 "onvalue"
也不包含 "offvalue"
(甚至不存在)时,会发生什么? 在这种情况下,复选按钮将进入特殊的 "tristate"
或不确定模式; 您有时会在用户界面中看到此信息,其中的复选框仅包含一个破折号,而不是空白或带有复选标记。 在这种状态下,将设置状态标志 "alternate"
,因此可以使用 "instate"
方法进行检查:
check.instate(['alternate'])
由于复选按钮不会自动设置(或创建)链接变量,因此您的程序需要确保将变量设置为适当的起始值。
5 单选按钮:ttk.Radiobutton
单选按钮使您可以在多个互斥的选项之一之间进行选择;与选择按钮不同,它不仅限于两个选择。 单选按钮始终在一组中一起使用,当选择的数量相当少(例如3-5)时,它是一个不错的选择。
单选按钮是使用 ttk.Radiobutton
函数创建的,通常作为一个集合来创建:
phone = StringVar()
home = ttk.Radiobutton(parent, text='Home', variable=phone, value='home')
office = ttk.Radiobutton(parent, text='Office', variable=phone, value='office')
cell = ttk.Radiobutton(parent, text='Mobile', variable=phone, value='cell')
单选按钮与复选按钮共享大多数相同的配置选项。 一个例外是将 "onvalue"
和 "offvalue"
选项替换为单个 "value"
选项。 该集合中的每个单选按钮将具有相同的链接变量,但具有不同的值。 当变量具有给定值时,单选按钮将被选中,否则未选中。 当链接的变量不存在时,单选按钮还会显示 "tristate"
或不确定状态,可以通过 "alternate"
状态标志进行检查。
看一个例子:
class RadioLove(Tk):
def __init__(self):
super().__init__()
self.title('独爱') # 设定标题
self.geometry('200x100') # 设定尺寸
self.var = StringVar()
self.lb = ttk.Label(background='yellow', width=20, text='')
self.lb.grid()
r1 = self.radio('选 苹果', "苹果")
r2 = self.radio('选 香蕉', "香蕉")
r3 = self.radio('选 橘子', "橘子")
[r.grid() for r in (r1, r2, r3)]
def print_love(self):
self.lb['text'] = f"钟爱于{self.var.get()}"
def radio(self, text, value):
kw = {
'text': text,
'variable': self.var,
'value': value,
'command': self.print_love
}
return ttk.Radiobutton(**kw)
root = RadioLove()
root.mainloop()
显示结果为:
6 ttk.Frame:框架
框架是显示为简单矩形框的小部件。框架主要用作其他小部件的容器。框架是使用 ttk.Frame
类创建的
frame = ttk.Frame(parent)
框架可以采用几种不同的配置选项改变其的显示方式。
6.1 要求的尺寸
像任何其他小部件一样,创建后,它通过(父)几何管理器添加到用户界面。 通常,框架将向几何管理器请求的大小将由其中包含的任何小部件的大小和布局确定(这些小部件在管理框架本身内容的几何管理器的控制下)。
如果出于某种原因,您想要一个不包含其他小部件的空框架,则应该使用 "width"
或者 "height"
配置选项显式设置框架从其父几何管理器请求的尺寸(否则,最终会得到一个很小的框架)。
一般地,几何管理器的尺寸(Size)使用 "width"
或者 "height"
的配置选项进行设定,其单位默认是 pixel(像素)。比如 "350","350c","350i","350p" 分别表示“350像素”,“350厘米”,“350英寸”,“350打印机点(1/72 英寸)”。
6.2 Padding:填充
"padding"
配置选项用于在窗口小部件内部请求额外的空间。这样,如果您要在框架中放置其他小部件,则始终会有一些余量。 单个数字始终指定相同的填充,两个数字的列表可让您指定水平和垂直填充,而四个数字的列表可让您依次指定左侧,顶部,右侧和底部的填充。比如:
frame['padding'] = (5,10)
6.3 边框:Borders
为此,您需要设置 "borderwidth"
配置选项(默认为 0,即没有边框),以及 "relief"
选项,该选项指定边框的视觉外观: "flat" (默认),"raised", "sunken" ,"solid","ridge" 或 "groove"。比如:
frame['borderwidth'] = 2
frame['relief'] = 'sunken'
6.4 改变风格
还有一个 "style"
配置选项,这对于所有主题小部件都是通用的,它可以让您控制其外观或行为的几乎任何方面。比如:
style = ttk.Style()
style.configure("BW.TLabel", foreground="black", background="white")
l1 = ttk.Label(text="Test", style="BW.TLabel")
l2 = ttk.Label(text="Test", style="BW.TLabel")
7 一个例子:创建一个英尺转换为米的工具
代码:
from tkinter import ttk, Tk, StringVar
from tkinter import N, W, E, S
class App(ttk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.feet = StringVar()
self.meters = StringVar()
self._layout()
for child in self.winfo_children():
child.grid_configure(padx=5, pady=5)
@property
def widgets(self):
_widgets = [
[ttk.Entry(self, width=14, textvariable=self.feet),
ttk.Label(self, text="feet")],
[ttk.Label(self, text="is equivalent to"),
ttk.Label(self, textvariable=self.meters),
ttk.Label(self, text="meters")],
[ttk.Button(self, text="Calculate", command=self.calculate)]
]
return _widgets
def grid_layout(self):
for n_row, row in enumerate(self.widgets):
for n_col, widget in enumerate(row):
widget.grid(column=n_col, row=n_row, sticky=(E, W))
def _layout(self):
# 设定 master 的 Resize
self.master.columnconfigure(0, weight=1)
self.master.rowconfigure(0, weight=1)
self['padding'] = 3, 3, 12, 12
self.grid(column=0, row=0, sticky=(N, W, E, S))
self.grid_layout()
def calculate(self, *args):
try:
value = float(self.feet.get())
self.meters.set((0.3048 * value * 10000.0 + 0.5)/10000.0)
except ValueError:
pass
root = Tk()
root.title('Feet to Meters')
app = App(master=root)
app.mainloop()
显示结果为:
为了支持
(键盘回车)键盘触发事件,可以这样:
class App1(App):
def __init__(self, master=None):
super().__init__(master)
self.widgets[0][0].focus()
self.master.bind('', self.calculate)
# 键盘触发事件
root = Tk()
root.title('Feet to Meters')
app = App1(master=root)
app.mainloop()
如果想要为 ttk.Frame
添加统一的配置,可以这样:
class App(ttk.Frame):
def __init__(self):
super().__init__()
self.option_add("*Font", "arial 40 bold") # 添加统一的字体设定