Python Tkinter(三)

🍦 Tkinter是Python自带的GUI编程工具包,适合快速开发桌面应用程序,使用Tk图形库,提供创建窗口、按钮等常用UI的功能。

1 滚动条

  • 滚动条
    • Scrollbar控件常常用于创建一个水平或垂直的滚动条。
    • 通常与Listbox、Text、Canvas及Entry等控件一起使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
from tkinter import *

# 调用Tk()创建主窗口
window = Tk()
window.geometry("500x250")
window.resizable(0, 0)

# 创建一个滚动条控件,默认为垂直方向
sbar1= Scrollbar(
window,

# 滚动条宽度,默认值16px【外观与样式】
width=30,

# 边框宽度,默认值0
borderwidth=0,

# 指定滚动条和箭头的边框宽度,默认值-1
elementborderwidth=-1,

# 鼠标在滑块上方飘过时滑块的样式
# 默认值raised,可选值flat、sunken、groove、ridge
activerelief="ridge",

# 鼠标在上方飘过时滑块和箭头的背景颜色,默认由系统决定
activebackground="red",

# 背景颜色,默认值由系统指定
background="blue",

# 凹槽的颜色,默认由系统指定
troughcolor="green",

# 鼠标在上方飘过的时的鼠标样式,默认值由系统指定
# 参数值:arrow、circle、cross、plus
cursor="circle",

# 滚动条更新时回调的函数【行为与功能】
# 通常指定对应组件的xview()或yview()方法
# command="",

# 指定当用户拖拽滚动条时的行为
# 默认值False,滚动条的任何变动都会即刻调用command回调函数
# 设为True则当用户松开鼠标才调用
jump=False,

# 绘制horizontal垂直滚动条或vertical水平滚动条,默认值水平
# orient=HORIZONTAL,

# 鼠标左键点击滚动条凹槽时的响应时间,默认值300毫秒
repeatdelay=300,

# 鼠标左键紧按滚动条凹槽时的响应间隔,默认值100毫秒
repeatinterval=100,

# 使用Tab键可将焦点移到该Scrollbar组件上【焦点与交互】
# 默认为开启,可将该选项设为False,避免焦点在此组件上
takefocus=False
)

# 将滚动条放置在右侧,并设置当窗口大小改变时滚动条沿垂直方向延展
sbar1.pack(side=RIGHT, fill=Y)

# 创建水平滚动条,默认为水平方向,拖动窗口时沿X轴方向填充
sbar2 = Scrollbar(window, orient=HORIZONTAL)
sbar2.pack(side=BOTTOM, fill=X)

# 创建列表框控件,并添加两个滚动条(垂直和水平方向)
mylist = Listbox(
window,
xscrollcommand=sbar2.set,
yscrollcommand=sbar1.set
)

for i in range(30):
mylist.insert(
END,
"博客地址:https://stitch-top.github.io/"+\
":第"+str(i+1)+"次"+"\n"
)

# 当窗口改变大小时会在X与Y方向填满窗口
# mylist.pack(side=LEFT,fill = BOTH)
mylist.pack(fill = BOTH)

# 使用command关联控件的yview、xview方法
sbar1.config(command=mylist.yview)
sbar2.config(command=mylist.xview)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

2 对话框

  • 对话框
    • messagebox:消息对话框。
    • filedailog:文件选择对话框。
    • colorchooser:颜色选择对话框。

2-1 filedailog

  • filedailog
    • 文件对话框在GUI中经常使用,比如:上传文件、文件打开或保存等操作。
    • Tkinter提供的文件对话框filedailog被封装在tkinter.filedailog模块中。

(1) 上传文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from tkinter import *
import tkinter.filedialog

# 调用Tk()创建主窗口
window = Tk()
window.geometry("500x250")
window.resizable(0, 0)


# 定义一个处理文件的相关函数
def ask_file():
# Open():打开文件
# SaveAs():打开一个保存文件的对话框
# askopenfile():打开文件,并返回文件流对象
# askopenfiles():打开多个文件,并以列表形式返回多个文件流对象
# askopenfilename():从本地选择一个文件,并返回文件的路径
# askopenfilenames():同时打开多个文件,并以元组形式返回文件名
# asksaveasfile():选择类型保存文件,并返回文件流对象
# asksaveasfilename():选择文件名保存文件,并返回文件名
# askdirectory:选择目录,并返回目录名
file_name = tkinter.filedialog.askopenfilename()
if file_name != "":
label.config(text=file_name)
else:
label.config(text="您未选择任何文件!")


btn = Button(
window, text="选择文件", relief=RAISED, command=ask_file
)
btn.grid(row=0, column=0, padx=5, pady=5)

label = Label(window, text="", bg="#87CEEB")
label.grid(row=0, column=1, padx=5, pady=5)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

(2) 保存文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import os
from tkinter import *
from PIL import Image
from tkinter import filedialog
import tkinter.messagebox as messagebox

# 调用Tk()创建主窗口
window = Tk()
window.geometry("500x250")
window.resizable(0, 0)

file_name = StringVar()
path_vars = StringVar()

def open_file():
# global file_data
try:
# 打开文件,返回该文件的完整路径
file_path = filedialog.askopenfilename()
file_name.set(file_path)
if file_path:
file_data = Image.open(file_path)
return file_data
else:
messagebox.showwarning("警告", "您未选择任何文件!")
return None
except Exception as e:
messagebox.showerror("错误", f"啊哦~打开文件报错了:{e}")
return None


def save_file():
# 调用open_file获取file_data
file_data = open_file()
try:
if file_data is None:
messagebox.showwarning("警告", "请先选择文件!")
return

file_types = [
("PNG", "*.png"), ("JPG", "*.jpg"),
("GIF", "*.gif"), ("txt files", "*.txt"),
("All files", "*")
]
# 该函数只返回选择文件的文件名,不具备保存文件的能力
file_new_path = filedialog.asksaveasfilename(
# 指定文件对话框的标题
title="保存文件",

# 指定筛选文件类型的下拉菜单选项,值由二元组构成
filetypes=file_types,

# 指定文件后缀名,若保存时自动添加,则该选项值不生效
defaultextension="*.png",

# 指定打开或保存文件的默认路径,默认路径是当前文件夹
initialdir=os.path.expanduser("~"),

# 若不指定该选项,那么对话框默认显示在根窗口上
# 通过设置该参数,可以使得对话框显示在子窗口上
# parent=""
)
if file_new_path:
# 获取文件扩展名
_, ext = os.path.splitext(file_new_path)

# 如果没有扩展名,则添加.png
if not ext:
file_new_path += ".png"

file_data.save(file_new_path)
messagebox.showinfo("成功", "文件已保存!")
else:
messagebox.showwarning("警告", "您未选择保存路径!")
except Exception as e:
messagebox.showerror("错误", f"啊哦~保存文件报错了:{e}")


# 定义读取文件的组件
entry_o = Entry(window, textvariable=file_name, width=60)
entry_o.grid(row=1, column=0, padx=5, pady=5)
Button(
window, text="选择文件", command=open_file
).grid(row=1, column=1, padx=5, pady=5)

# 定义保存文件的组件
entry_s = Entry(window, textvariable=path_vars, width=60)
entry_s.grid(row=2, column=0, padx=5, pady=5)
Button(
window, text="保存文件", command=save_file
).grid(row=2, column=1, padx=5, pady=5)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

2-2 messagebox

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from tkinter import *
import tkinter.messagebox as messagebox

# 调用Tk()创建主窗口
window = Tk()
# window.geometry("500x250")
# window.resizable(0, 0)

# 隐藏主窗口,只显示消息框
window.withdraw()

yesno_result = messagebox.askyesno(
"是/否对话框", "您想继续吗?"
)
question_result = messagebox.askquestion(
"是/否对话框", "您喜欢奶酪吗?"
)
okcancel_result = messagebox.askokcancel(
title="确定/取消对话框", message="您确定要继续吗?"
)
retrycancel_result = messagebox.askretrycancel(
title="重试/取消对话框", message="发生错误,请重试!"
)

messagebox.showinfo("信息对话框", "这是一些重要信息。")
messagebox.showerror("错误对话框", "啊哦~发生一个错误!")
messagebox.showwarning(
title="警告对话框", message="这是一个警告消息!"
)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

2-3 colorchooser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from tkinter import *
from tkinter import colorchooser

# 调用Tk()创建主窗口
window = Tk()
window.geometry("500x250")
window.resizable(0, 0)


def call_back():
# 打开颜色对话框
color_value = colorchooser.askcolor()

# 在颜色面板点击确定后,会在窗口显示二元组颜色值
label.config(text="颜色值:"+ str(color_value))


label = Label(window, text="", font=("宋体", 10))
label.pack()
Button(
window, text="点击选择颜色", command=call_back,
width=15, bg="#9AC0CD"
).pack()

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

3 事件处理

  • 事件处理
    • 事件处理,是GUI程序中不可或缺的组成部分,也是实现人机交互的关键。
    • 控件是组成一台机器的零部件,而事件处理则是驱动机器正常运转的关键。
    • Tkinter提供的事件处理机制允许为控件绑定相应的事件和事件处理函数callback。
    • 事件类型也叫事件码,是Tkinter模块规定的,包括鼠标、键盘、光标等相关事件。

3-1 鼠标事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
from tkinter import *
import tkinter.messagebox as messagebox

# 调用Tk()创建主窗口
window = Tk()
window.geometry("500x250")
window.resizable(0, 0)

# 鼠标左键单击:<ButtonPress-1>,简写<Button-1>
# 鼠标左键释放:<ButtonRelease-1>
# 1、2、3分别代表左键、滑轮、右键
button1 = Button(window, text="左键单击我")
button1.bind(
"<Button-1>",
lambda event: messagebox.showinfo("单击", "鼠标左键单击!")
)
button1.pack(pady=5)

# 鼠标左键双击:<Double-Button-1>
button2 = Button(window, text="左键双击我")
button2.bind(
"<Double-Button-1>",
lambda event: messagebox.showinfo("双击", "鼠标左键双击!")
)
button2.pack(pady=5)

# 鼠标右键单击:<Button-3>
button3 = Button(window, text="右键单击我")
button3.bind(
"<Button-3>",
lambda event: messagebox.showinfo("单击", "鼠标右键单击!")
)
button3.pack(pady=5)

# 按住鼠标左键移动:<B1-Motion>
canvas = Canvas(window, width=200, height=50, bg="lightgray")
canvas.bind(
"<B1-Motion>",
lambda event: messagebox.showinfo(
"按住移动", f"鼠标移动中:x={event.x},y={event.y}!"
)
)
canvas.pack(pady=10)

# 转动鼠标滑轮:<MouseWheel>
canvas.bind(
"<MouseWheel>",
# event.delta表示滚轮滚动方向和幅度
lambda event: messagebox.showinfo(
"转动滑轮", f"鼠标滚轮滚动:{event.delta}!")
)


def on_enter(event):
messagebox.showinfo("光标进入", "鼠标进入Label!")


def on_leave(event):
messagebox.showinfo("光标离开", "鼠标离开Label!")


label = Label(
window, text="悬停在我上面", relief="groove", width=20
)

# 鼠标光标进入控件实例:<Enter>
label.bind("<Enter>", on_enter)

# 鼠标光标离开控件实例:<Leave>
label.bind("<Leave>", on_leave)

label.pack(pady=10)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

3-2 键盘事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from tkinter import *
import tkinter.messagebox as messagebox

# 调用Tk()创建主窗口
window = Tk()
window.geometry("500x250")
window.resizable(0, 0)

# 按下键盘上的任意键:<Key>
entry1 = Entry(window)
entry1.bind(
# 获取按键字符:event.char
"<Key>",
lambda event: messagebox.showinfo(
"按键按下", f"按下按键:{event.char}!"
)
)
entry1.pack(pady=5)

# 按下键盘上的某字母或数字键:<KeyPress-字母>、<KeyPress-数字>
# 释放键盘上的按键:<KeyRelease>
entry2 = Entry(window)
entry2.bind(
"<KeyPress-a>",
lambda event: messagebox.showinfo(
"按键按下", "按下\"a\"键!"
)
)
entry2.pack(pady=5)

# 按下回车键:<Return>
# 空格键:<Space>
# 方向键:<UP>/<Down>/<Left>/<Right>
# 常用功能键:<F1>、...、<F12>
entry3 = Entry(window)
entry3.bind(
"<Return>",
lambda event: messagebox.showinfo(
"按键按下", "按下回车键!"
)
)
entry3.pack(pady=5)

# 组合键:(Ctrl + A)
entry4 = Entry(window, width=20)
entry4.bind(
"<Control-a>",
lambda event: messagebox.showinfo(
"按键按下", "按下:Ctrl + A!"
)
)
entry4.pack(pady=5)

# 组合键:(Ctrl + Shift + T)
# Control、Alt、Shift、处于大写锁定Lock
# 双击Double、三次Triple、四次Quadruple
entry5 = Entry(window,width=20)
entry5.bind(
"<Control-Shift-KeyPress-T>",
lambda event: messagebox.showinfo(
"按键按下", "按下:Ctrl + Shift + T!"
)
)
entry5.pack(pady=5)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

3-3 焦点事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from tkinter import *
import tkinter.messagebox as messagebox

# 调用Tk()创建主窗口
window = Tk()
window.geometry("500x250")
window.resizable(0, 0)

# 焦点获得:<FocusIn>
# 焦点失去:<FocusOut>
entry = Entry(window)
entry.bind(
"<FocusIn>",
lambda event: messagebox.showinfo("提示", "输入框获得焦点!")
)
entry.bind(
"<FocusOut>",
lambda event: messagebox.showinfo("提示", "输入框失去焦点!")
)
entry.pack(pady=5)

# 记录上一次窗口尺寸
last_width = window.winfo_width()
last_height = window.winfo_height()


# 监听窗口大小改变事件
def on_configure(event):
global last_width, last_height
if event.width != last_width or event.height != last_height:
messagebox.showinfo(
"提示",
f"窗口大小改变:width={event.width},height={event.height}!"
)
last_width = event.width
last_height = event.height


# 当控件被销毁时触发执行事件的函数:<Destroy>
# 当窗口或组件的某部分不再被覆盖时触发事件:<Expose>
# 控件发生改变时触发事件,比如调整控件大小:<Configure>
# 当控件状态从“激活”变为“未激活”时触发事件:<Deactivate>
# 当应用程序至少有一部分在屏幕中是可见状态时触发事件:<Visibility>
window.bind("<Configure>", on_configure)

# 窗口关闭
window.protocol(
"WM_DELETE_WINDOW",
lambda: messagebox.showinfo("提示", "窗口关闭!")
)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

4 布局方法

  • 布局方法
    • Tkinter提供了三种较为常用的布局管理器,如下所示。
    • grid():以网格形式对控件进行排列,此方法较为灵活。
    • pack():按控件的添加顺序进行排列,此方法灵活性较差。
    • place():指定组件大小及摆放位置,三个方法中最为灵活。

4-1 grid()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from tkinter import *

# 调用Tk()创建主窗口
window = Tk()
window.geometry("500x250")
window.resizable(0, 0)

# 在窗口内创建按钮,以表格的形式依次排列
for x in range(7):
for y in range(7):
Button(
window,
text="(" + str(x) + ", " + str(y) + ")",
bg="#D1EEEE"
).grid(row=x, column=y)

# 在第4行第9列添加一个Label标签
Label(
window, text="stitch-top.github.io",
fg="blue", font=("楷体", 11, "bold")
).grid(
# 第4行,窗体最上面为起始行,默认第0行
row=3,

# 所跨行数,默认为1行,通过该参数可合并一列中多个邻近单元格
rowspan=1,

# 第9列,窗口最左边为起始列,默认第0列
column=8,

# 所跨列数,默认为1列,通过该参数可合并一行中多个邻近单元格
columnspan=1,

# 控制内边距,在单元格内部,左右、上下方向上填充指定大小的空间
ipadx=5, ipady=5,

# 控制外边距,在单元格外部,左右、上下方向上填充指定大小的空间
padx=10, pady=10,

# 设置控件位于单元格方位上,值与anchor相同,默认center
# 如果不设置该参数,则控件在单元格内居中
# 左上nw、上n、右上ne、左w、右e、左下sw、下s、右下se
sticky="e"
)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

4-2 pack()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from tkinter import *

# 调用Tk()创建主窗口
window = Tk()
window.geometry("500x250")
window.resizable(0, 0)

Label(
window, text="红色",
bg="red", fg="#ffffff", relief=GROOVE
).pack(
# 组件在窗口中的对齐方式,默认center
# 左上nw、上n、右上ne、左w、右e、左下sw、下s、右下se
anchor="center",

# 是否可扩展窗口,扩展True,不扩展False
# 若为True,则控件的位置始终位于窗口的中央位置
expand=True,

# 允许控件在水平或垂直方向上进行拉伸
# 参数值:X、Y、BOTH、NONE
# 控件会占满水平方向上的所有剩余空间
# fill=X,

# 与fill参数一起使用,
# 组件与内容、组件边框的距离,即内边距
# 例如文本内容与组件边框的距离
# 单位:像素p、厘米c、英寸i
ipadx=5, ipady=5,

# 组件之间的上下、左右的距离,即外边距
# 单位:像素p、厘米c、英寸i
padx=10, pady=10,

# 组件放置在窗口位置上,值top、bottom、left、right
# 注意单词小写时需使用字符串格式,大写则不必
side="bottom"
)

Label(
window, text="蓝色",
bg="blue", fg="#ffffff", relief=GROOVE
).pack(fill=X, pady="5px")

Label(
window, text="绿色",
bg="green", fg="#ffffff", relief=RAISED
).pack(side=LEFT, expand=1, fill=BOTH)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

4-3 place()

  • place()
    • 与前两种布局方法比,采用place()方法进行布局管理要更加精细化。
    • 直接指定控件在窗体内的绝对位置,或相对于其他控件定位的相对位置。
    • 参数说明
      • relx和rely参数,指定的是控件相对于父组件的位置方向。
      • relwidth和relheight,指定控件相对于父组件的尺寸大小。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from tkinter import *

# 调用Tk()创建主窗口
window = Tk()
window.geometry("500x250")
window.resizable(0, 0)

# 创建一个frame窗体对象,用来包裹标签
frame = Frame(
window, relief=SUNKEN,
borderwidth=2, width=300, height=200
)

# 在水平、垂直方向上填充窗体
frame.pack(side=TOP, fill=BOTH, expand=1)

# 标签位置距离窗体左上角(40, 40)处,大小(width,height)
Label(
frame, text="位置1", bg="orange", fg="white"
).place(x=40, y=40, width=60, height=30)

# 以右上角进行绝对值定位,anchor=NE
# 标签位置距离窗体左上角(190, 40)处
Label(
frame, text="位置2", bg="blue", fg="white"
).place(x=190, y=40, anchor=NE, width=60, height=30)

# 水平起始位置相对于窗体水平距离的0.44倍
# 垂直的绝对距离为40(高),大小为(60,30)
Label(
frame, text="位置3", bg="green", fg="white"
).place(relx=0.44, y=40, width=60, height=30)

# 水平起始位置相对于窗体水平距离的0.61倍
# 垂直的绝对距离为40,宽度60,高度为窗体高度比例的0.12倍
Label(
frame, text="位置4", bg="purple", fg="white"
).place(relx=0.61, y=40, width=60, relheight=0.12)

# 标签坐标是否考虑边界宽度,默认值INSIDE包含边界,排除边界OUTSIDE
Label(
frame, text="位置5", bg="pink", fg="white"
).place(bordermode=INSIDE, x=390, y=40, width=60, height=30)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

5 布局控件

  • 布局控件
    • Tkinter提供了几个常用的布局管理控件,比如:Frame、Toplevel、LabelFrame、PanedWindow等。
    • 主要作用是为其他控件提供载体,并将主窗口划分成多区域,方便开发对不同区域进行设计与管理。

5-1 Frame

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
from tkinter import *

# 调用Tk()创建主窗口
window = Tk()
window.geometry("500x250")
window.resizable(0, 0)

# 主窗口上添加一个frame控件
frame = Frame(
window,

# 背景颜色【外观属性】
bg="gray",

# 边框宽度
bd=10,

# 边框样式,参数值sunken、raised、groove、ridge、flat(默认)
relief="flat",

# 指定Frame组件及其子组件的颜色映射
colormap="new",

# 鼠标在Frame上飘过的样式,默认由系统指定
# 参数值:arrow、circle、cross、plus
cursor="plus",

# Frame未获得焦点时高亮边框的颜色,通常由系统指定为标准色
highlightbackground="green",

# Frame获得焦点时高亮边框的颜色
highlightcolor="blue",

# 高亮边框的宽度,默认值0
highlightthickness=10,

# Frame的高度和宽度【尺寸属性】
height=250, width=300,

# 距离主窗口在水平、垂直方向上的外边距
padx=10, pady=10,

# True则窗体被当成容器使用,一些程序也可被嵌入【交互属性】
container=False,

# 默认False,是否接受输入焦点(通过Tab键将焦点转移上来)
takefocus=True
)
frame.pack()

# 在frame上添加两个frame,左右各一个
frame_left = Frame(frame)
Label(
frame_left, text="左侧标签1",
bg="orange", width=20, height=3
).grid(row=0, column=0)
Label(
frame_left, text="左侧标签2",
bg="light blue", width=20, height=3
).grid(row=1, column=0)
frame_left.pack(side=LEFT)

frame_right = Frame(frame)
Label(
frame_right, text="右侧标签1",
bg="light green", width=20, height=3
).grid(row=0, column=0)
Label(
frame_right, text="右侧标签2",
bg="pink", width=20, height=3
).grid(row=1, column=0)
frame_right.pack(side=RIGHT)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

5-2 Toplevel

  • Toplevel
    • Topleve是一个顶级窗口控件,也被叫做“子窗体”控件。
    • 该控件会脱离主窗口另行创建,存在形式不同于其他容器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
from tkinter import *
import tkinter.messagebox as messagebox

# 调用Tk()创建主窗口
window = Tk()
window.title("主窗体")
window.geometry("500x250")
window.resizable(0, 0)


def create_toplevel():
toplevel = Toplevel()
toplevel.title("子窗体1")
toplevel.geometry("300x100")
toplevel.resizable(0, 0)
Label(
toplevel, text="https://stitch-top.github.io/",
bg="#9BCD9B", font=("宋体", 11)
).pack()


top = Toplevel(window)
top.title("子窗体2")
top.geometry("300x100")


# 窗口最小化
def hide_window():
top.iconify()


# 移除窗口,并未销毁
def withdraw_window():
top.withdraw()


# 显示窗口,结合iconify()或withdraw()使用
def show_window():
top.deiconify()


# 返回一个系统特定的窗口识别码
def get_code():
code = top.frame()
messagebox.showinfo("窗口识别码", f"窗口识别码:{code}!")


# 设置和获得当前窗口的状态
# normal正常状态,withdrawn移除窗口、iconic最小化、zoomed放大
def check_state():
state = top.state()
messagebox.showinfo("窗口状态", f"当前窗口状态:{state}!")


def open_transient_window():
transient_window = Toplevel(window)
transient_window.title("子窗体3")
transient_window.geometry("300x100")

# 将该窗口设置为主窗口的临时窗口
transient_window.transient(window)

# 关闭事件
transient_window.protocol(
# 窗口被关闭:WM_DELETE_WINDOW
# 窗口被保存:WM_SAVE_YOURSELF
# 窗口获得焦点:WM_TAKE_FOCUS
"WM_DELETE_WINDOW", transient_window.destroy
)


def create_group_window():
group_window1 = Toplevel(top)
group_window1.title("组窗口1")
group_window1.geometry("260x100")

# 将此窗口加入窗口群组top中
group_window1.group(top)


def create_another_group_window():
group_window2 = Toplevel(top)
group_window2.title("组窗口2")
group_window2.geometry("260x100")

# 将此窗口加入窗口群组top
group_window2.group(top)


Button(
window, text="创建窗口1",
width=20, height=1, command=create_toplevel
).pack()
Button(
window, text="最小化窗口2",
width=20, height=1, command=hide_window
).pack()
Button(
window, text="移除窗口2",
width=20, height=1, command=withdraw_window
).pack()
Button(
window, text="显示窗口2",
width=20, height=1, command=show_window
).pack()
Button(
window, text="获取窗口2的识别码",
width=20, height=1, command=get_code
).pack()
Button(
window, text="检查窗口2的状态",
width=20, height=1, command=check_state
).pack()
Button(
window, text="打开窗口3",
width=20, height=1, command=open_transient_window
).pack()

Button(
top, text="创建组窗口1",
width=10, height=1, command=create_group_window
).pack()
Button(
top, text="创建组窗口2",
width=10, height=1, command=create_another_group_window
).pack()

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

5-3 LabelFrame

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from tkinter import *

# 调用Tk()创建主窗口
window = Tk()
window.geometry("500x250")
window.resizable(0, 0)

# 定义一个容器,labelanchor用来设置标题的方位
frame_left = LabelFrame(
window, text="水果", labelanchor=W, bg="light blue"
)
frame_left.place(relx=0.2, rely=0.2, relwidth=0.25, relheight=0.5)

Label(frame_left, text="西瓜").place(relx=0.2, rely=0.2)
Label(frame_left, text="葡萄").place(relx=0.6, rely=0.2)
Label(frame_left, text="苹果").place(relx=0.2, rely=0.6)
Label(frame_left, text="香蕉").place(relx=0.6, rely=0.6)

frame_right = LabelFrame(
window, text="编程", labelanchor=W, bg="light green"
)
frame_right.place(relx=0.5, rely=0.2, relwidth=0.3, relheight=0.5)

Label(frame_right, text="Java").place(relx=0.2, rely=0.2)
Label(frame_right, text="Python").place(relx=0.6, rely=0.2)
Label(frame_right, text="Golang").place(relx=0.2, rely=0.6)
Label(frame_right, text="C++").place(relx=0.7, rely=0.6)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

5-4 PanedWindow

  • PanedWindow
    • PanedWindow(窗格界面)是特殊的Frame控件,Tkinter8.4版本后新增的空间管理组件。
    • 主要目的是为其他组件提供一个容器或框架,从而实现以分块的形式对图形界面进行布局。
    • 允许自主调整界面划分,以及每块区域的大小,即提供手柄功能(showhandle=True启用)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from tkinter import *

# 调用Tk()创建主窗口
window = Tk()
window.geometry("500x250")
window.resizable(0, 0)

# 创建一个水平方向的PanedWindow,并添加到主窗口中,默认水平方向
panedwindow1 = PanedWindow(window)
panedwindow1.pack(fill=BOTH, expand=1)

# 在窗口区的左侧添加两个水平方向的Label
label1 = Label(
panedwindow1, text="西瓜", bg="light green",
width=10, font=("微软雅黑", 11)
)
panedwindow1.add(label1)
label2 = Label(
panedwindow1, text="葡萄", bg="#7171C6",
width=10, font=("微软雅黑", 11)
)
panedwindow1.add(label2)

# 创建一个垂直方向的PanedWindow,并添加一个手柄,设置分割线样式
panedwindow2 = PanedWindow(
orient=VERTICAL, showhandle=True, sashrelief="sunken"
)

# 添加到panedwindow1中
panedwindow1.add(panedwindow2)

# 在panedwindow2中添加两个垂直方向的标签
label3 = Label(
panedwindow2, text="橘子", bg="orange",
width=10, height=5, font=("微软雅黑", 11)
)
panedwindow2.add(label3)
label4 = Label(
panedwindow2, text="苹果", bg="light blue",
width=10, font=("微软雅黑", 11)
)
panedwindow2.add(label4)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

(1) 基本属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
from tkinter import *

# 调用Tk()创建主窗口
window = Tk()
window.geometry("500x250")
window.resizable(0, 0)

# 创建PanedWindow控件,设置为垂直方向
paned_window = PanedWindow(
window,

# 窗格分布方式,默认水平horizontal,垂直vertical
orient="vertical",

# 设置PanedWindow的高度、宽度,若不设则由子组件的尺寸决定
height=200, width=200,

# 设置是否显示调节窗格的手柄,默认False
showhandle=True,

# 手柄位置,例如当orient="vertical"时
# handlepad表示分割线上的手柄与左端的距离,默认8像素
handlepad=10,

# 手柄尺寸,必须是正方形手柄,默认8像素,即设置正方形边长
handlesize=20,

# 定义用户调整窗格尺寸的操作
# 默认True,窗格的尺寸随用户鼠标的拖拽而改变
# 设为False,窗格的尺寸在用户释放鼠标时才会更新到新位置上
opaqueresize=True,

# 设置每一条分割线到窗格间的间距
sashpad=5,

# 分割线的样式,默认flat,还有sunken、raised、groove、ridge
sashrelief="raised",

# 设置分割线的宽度
sashwidth=5,

# 边框样式,默认flat,还有sunken、raised、groove、ridge
relief="sunken"
)

# 创建两个子窗口
frame1 = Frame(
paned_window, bg="light blue", width=300, height=100
)
frame2 = Frame(
paned_window, bg="light green", width=300, height=100
)

# 添加子窗口到PanedWindow
paned_window.add(frame1)
paned_window.add(frame2)

# 显示PanedWindow
paned_window.pack(fill=BOTH, expand=True)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

(2) 常见方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from tkinter import *

# 调用Tk()创建主窗口
window = Tk()
window.title("主窗体")
window.geometry("500x250")
window.resizable(0, 0)

# 创建PanedWindow控件,设置为垂直方向
paned_window = PanedWindow(window, orient="vertical")

# 创建三个子框架
frame1 = Frame(
paned_window, bg="light blue", width=300, height=30
)
frame2 = Frame(
paned_window, bg="light green", width=300, height=30
)
frame3 = Frame(
paned_window, bg="light coral", width=300, height=30
)

# 添加子框架到PanedWindow
paned_window.add(frame1)
paned_window.add(frame2)
paned_window.add(frame3)

# 修改frame2的背景色为黄色
# frame2.config(bg="yellow")

# 从PanedWindow中删除frame1
# paned_window.forget(frame1)

# 返回一个二元组表示指定分割线的起点坐标
coords = paned_window.sash_coord(0)
print("第一个分割线坐标:", coords)

# 移动第一个分割线到新位置(50, 50)
paned_window.sash_place(0, 50, 50)

# 将父组件中包含的子组件以列表的形式返回
children = paned_window.panes()
print("获取当前的子组件:", children)

# 最后将PanedWindow显示在主窗口中
paned_window.pack(fill=BOTH, expand=True)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

6 数字时钟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from tkinter import *
from time import strftime

# 调用Tk()创建主窗口
window = Tk()
window.geometry("500x250")
window.resizable(0, 0)

# 设置文本标签
label = Label(
window, font=("微软雅黑", 50, "bold"),
bg="light blue", fg="green"
)
label.pack(anchor="center", fill=BOTH, expand=1)

# 定义一个mode标志
mode="time"


# 定义显示时间的函数
def show_time():
if mode == "time":
# 时间格式化处理
string = strftime("%H:%M:%S %p")
else:
string = strftime("%Y-%m-%d")

label.config(text=string)

# 每隔1秒执行time函数一次
label.after(1000, show_time)


# 定义鼠标处理事件,点击时间切换为日期样式显示
def mouse_click(event):
global mode
if mode == "time":
# 点击切换mode样式为日期样式
mode = "date"
else:
mode = "time"


label.bind("<Button>", mouse_click)

# 调用show_time()函数
show_time()

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

7 图像编辑器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import tkinter as tk
from PIL import Image, ImageTk, ImageOps
from tkinter import filedialog, messagebox


# 定义一个图像编辑器类
class SimpleImageEditor:
def __init__(self, window):
# 保存窗口对象
self.window = window
self.window.title("图像编辑器")

# 初始化图像属性
self.image = None
self.image_label = None

# 创建菜单,创建GUI组件
self.create_menu()
self.create_widgets()

def create_menu(self):
# 创建菜单,将菜单配置到窗口
menu = tk.Menu(self.window)
self.window.config(menu=menu)

# 创建文件菜单
file_menu = tk.Menu(menu, tearoff=0)
menu.add_cascade(label="文件", menu=file_menu)
file_menu.add_command(label="打开", command=self.open_image)
file_menu.add_command(label="保存", command=self.save_image)
file_menu.add_separator()
file_menu.add_command(label="退出", command=self.window.quit)

# 创建编辑菜单
edit_menu = tk.Menu(menu, tearoff=0)
menu.add_cascade(label="编辑", menu=edit_menu)
edit_menu.add_command(
label="灰度", command=self.convert_to_grayscale
)

def create_widgets(self):
# 创建标签用于显示图像
self.image_label = tk.Label(self.window)
self.image_label.pack(padx=10, pady=10)

def open_image(self):
# 打开文件对话框选择图像文件
file_path = filedialog.askopenfilename(
filetypes=[("Image files", "*.jpg;*.jpeg;*.png;*.bmp")]
)
if file_path:
self.image = Image.open(file_path)
self.show_image()

def show_image(self):
if self.image:
# 将PIL图像转换为Tkinter图像
photo = ImageTk.PhotoImage(self.image)
# 更新标签以显示图像
self.image_label.config(image=photo)
# 保存引用以防止被垃圾回收
self.image_label.image = photo

def convert_to_grayscale(self):
if self.image:
# 转换图像为灰度
grayscale_image = ImageOps.grayscale(self.image)
self.image = grayscale_image
self.show_image()
else:
messagebox.showwarning("警告", "请先打开一张图像!")

def save_image(self):
if self.image:
# 打开文件保存对话框
file_path = filedialog.asksaveasfilename(
defaultextension=".png",
filetypes=[
("PNG files", "*.png"),
("BMP files", "*.bmp"),
("JPEG files", "*.jpg"),
("JPEG files", "*.jpeg")
]
)
if file_path:
# 保存图像到指定路径
self.image.save(file_path)
else:
messagebox.showwarning("警告", "请先打开一张图像!")


if __name__ == "__main__":
# 创建主窗口
window = tk.Tk()
window.geometry("500x250")

# 创建图像编辑器实例
app = SimpleImageEditor(window)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

8 待办管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import json
import tkinter as tk
from tkinter import messagebox


# 定义一个待办事项管理器类
class TodoApp:
def __init__(self, window):
# 保存窗口对象
self.window = window
self.window.title("待办管理器")

# 创建一个列表框用于显示待办事项
self.todo_listbox = tk.Listbox(
self.window, selectmode=tk.SINGLE, width=50
)
self.todo_listbox.pack(pady=5)

# 创建一个输入框用于输入新的待办事项
self.entry = tk.Entry(self.window, width=50)
self.entry.pack(pady=5)

# 创建一个按钮用于添加待办事项
self.add_button = tk.Button(
self.window, text="添加事项",
command=self.add_todo
)
self.add_button.pack(pady=5)

# 创建一个按钮用于删除选中的待办事项
self.remove_button = tk.Button(
self.window, text="删除事项",
command=self.remove_todo
)
self.remove_button.pack(pady=5)

# 加载之前保存的待办事项
self.load_todos()

def add_todo(self):
# 获取输入框中的待办事项
todo = self.entry.get()
if todo:
# 将待办事项添加到列表框中
self.todo_listbox.insert(tk.END, todo)

# 清空输入框
self.entry.delete(0, tk.END)
else:
messagebox.showwarning("警告", "请先输入待办事项!")

def remove_todo(self):
try:
# 获取当前选中的待办事项索引
selected_index = self.todo_listbox.curselection()[0]

# 从列表框中删除选中的事项
self.todo_listbox.delete(selected_index)
except IndexError:
messagebox.showwarning("警告", "请先选择一个事项!")

def load_todos(self):
try:
# 从JSON文件中加载待办事项
with open(r"./file/todos.json", "r") as file:
todos = json.load(file)
for todo in todos:
# 将每个待办事项添加到列表框中
self.todo_listbox.insert(tk.END, todo)
except FileNotFoundError:
# 如果文件不存在,不做任何处理
pass

def save_todos(self):
# 将当前的待办事项保存到JSON文件
todos = self.todo_listbox.get(0, tk.END)
with open(r"./file/todos.json", "w") as file:
# 将待办事项转换为列表并写入JSON文件
json.dump(list(todos), file)

def on_close(self):
# 保存待办事项
self.save_todos()

# 关闭窗口
self.window.destroy()


if __name__ == "__main__":
# 创建主窗口
window = tk.Tk()

# 创建待办事项管理器实例
app = TodoApp(window)

# 设置窗口关闭时调用on_close方法
window.protocol("WM_DELETE_WINDOW", app.on_close)

# 设置窗口主循环,一直显示,直到窗口被关闭
window.mainloop()

Python Tkinter(三)
https://stitch-top.github.io/2025/02/13/python/python16-python-tkinter-san/
作者
Dr.626
发布于
2025年2月13日 01:15:13
许可协议