实用Python程序设计MOOC-第十四章tkinter图形界面程序设计

实用Python程序设计MOOC-第十四章tkinter图形界面程序设计

[TOC]

实用Python程序设计MOOC-第十四章tkinter图形界面程序设计

tkinter 图形界面编程

图形界面编程要点

  • 使用Python自带tkinter库,简单,但是简陋
  • 使用PyQt,精美,但是难学

  • 控件(widgets)
    按钮、列表框、单选框、多选框、编辑框……

  • 布局
    如何将控件摆放在窗口上合适的位置

  • 事件响应
    对鼠标点击、键盘敲击、控件被点击等操作进行响应

  • 对话框
    弹出一个和用户交互的窗口接受一些输入

tkinter控件

tkinter的常用控件

控件 描述
Button 按钮
Canvas 画布,显示图形如线条或文本
Checkbutton 多选框(方形)
Entry 单行编辑框(输入框)
Frame 框架,上面可以摆放多个控件
Label 标签 ,可以显示文本和图像
Listbox 列表框
Menubutton 带菜单的按钮
Menu 菜单
Message 消息,显示多行文本
OptionMenu 带下拉菜单的按钮
Radiobutton 单选框(圆形)
Scale 滑块标尺,可以做一定范围内的数值选择
Scrollbar 卷滚条,使内容在显示区域内上下滚动
Text 多行编辑框(输入框)
Toplevel 顶层窗口,可以用于弹出自定义对话框
Spinbox 微调输入框。可以输入数值,也可以用上下箭头微调数值
PanedWindow 滑动分割窗口。可以将一个窗口分成几块,交界处可以拖动,改变各块大小
LabelFrame 带文字标签的框架,上面可以摆放多个控件
1
2
3
4
5
6
7
import tkinter as tk

win = tk.Tk() #生成一个窗口
tk.Label(win,.....) #在窗口win上生成一个Label,该Label的母体是win
ckb = tk.Checkbutton(win,.....) #在窗口上生成一个Checkbutton
frm = tk.Frame(win,.....) #在窗口上生成一个Frame
bt = tk.Button(frm,......) #在frm上生成一个Button

tkinter的扩展控件

控件 描述
TreeView 树形列表
ProgressBar 进度条
Notebook 多页标签
LabeledScale 带文字的滑块标尺
Panedwindow 分栏窗口
1
2
3
4
from tkinter import ttk
#tk中的控件ttk中都有,且更美观,用法基本和tk一样,且ttk多出几个控件

tree = ttk.TreeView(win,......)

tkinter布局

用grid进行布局

  • pack布局,place布局(略)

  • grid布局在窗口上布置网格(grid),控件放在网格单元里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import tkinter as tk
win = tk.Tk() #创建窗口
win.title("Hello") #指定窗口标题
label1 = tk.Label(win,text="用户名: ") #创建属于win上的图文标签控件
label2 = tk.Label(win,text="密码: ")
etUsername = tk.Entry(win) #创建属于win的单行编辑框控件,用于输入用户名
etPassword = tk.Entry(win) #创建密码编辑框

label1.grid(row=0,column=0,padx=5,pady=5)#label1放在第0行第0列,上下左右都留白5像素
label2.grid(row=1,column=0,padx=5,pady=5)
etUsername.grid(row=0,column=1,padx=5,pady=5) #用户名输入框放在第0行第1列
etPassword.grid(row=1,column=1,padx=5,pady=5) #密码输入框放在第1行第1列

btLogin = tk.Button(win,text="登录") #创建属于win的按钮控件
btLogin.grid(row=2,column=0,columnspan=2,padx=5,pady=5)#btLogin放在第2行第0列,跨2列

win.mainloop() #显示窗口

默认情况下的grid规则

  • 一个单元格只能放一个控件,控件在单元格中居中摆放。

  • 不同控件高宽可以不同,因此网格不同行可以不一样高,不同列也可以不一样宽。但同一行的单元格是一样高的,同一列的单元格也是一样宽的。

  • 一行的高度,以该行中包含最高控件的那个单元格为准。单元格的高度,等于该单元格中摆放的控件的高度(控件如果有上下留白,还要加上留白的高度)。列宽度也是类似的处理方式。

  • 若不指定窗口的大小和显示位置,则窗口大小和网格的大小一样,即恰好能包裹所有控件;显示位置则由Python自行决定。

  • 如果指定了窗口大小,或者用户拖拽窗口边缘将窗口变大,就会发生网格小于窗口大小的情况。

  • win.geometry("800x500+200+100") 字母x
    设定窗口宽800像素,高500,左上角位于(200, 100)

  • 可以做到网格随着窗口大小变化自动变化,填满窗口,并且控件依然居中显示

1
2
3
4
5
6
7
8
9
10
win.geometry("500x200")
win.columnconfigure(0, weight=1)
#指定第0列增量分配权重为1
win.columnconfigure(1, weight=1)
win.rowconfigure(0, weight=1)
#指定第0行增量分配权重为1
win.rowconfigure(1, weight=1)
win.rowconfigure(2, weight=1)

win.mainloop()

行列默认增量分配权重为0,宽高不会随着窗口大小变化而变化
窗口增大的时候会按照增量分配权重值的大小按比例分配

grid()函数的sticky参数

  • sticky指明控件在单元格中的“贴边方式”,即是否要贴着单元格的四条边。该参数可以是个字符串,包含”E”,”W”,”S”,”N”四个字符中的一个或多个。

不设置默认居中

1
2
3
4
5
6
7
8
9
10
11
label2.grid(row=1,column=0,padx=5,pady=5,sticky="NE")
#密码标签靠左上角

etUsername.grid(row=0,column=1,padx=5,pady=5,sticky="E")
#用户名编辑框靠右

etPassword.grid(row=1,column=1,padx=5,pady=5,sticky="EWSN")
#密码编辑框占满单元格

btLogin.grid(row=2,column=0,columnspan=2,padx=5,pady=5,sticky="SW")
#登录按钮靠左下

使用Frame控件进行布局

  • 控件多了,要算每个控件行、列、 rowspan,columnspan很麻烦

  • Frame控件上面还可以摆放控件,可以当作底板使用

  • 可以在Frame控件上面设置网格进行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
import tkinter as tk
win = tk.Tk()
win.title('人事系统')
frm01Red = tk.Frame(win,bg="red",highlightthickness=2) #背景红色,边框宽度2
frm01Red.grid(row=0,column=1,columnspan=2,sticky="WE")
tk.Label(frm01Red, text="姓名: ").grid(row=0,column=0,padx=6,pady=6)
tk.Entry(frm01Red).grid(row=0,column=1,padx=6,pady=6)
tk.Label(frm01Red, text="手机号: ").grid(row=0,column=2,padx=6,pady=6)
tk.Entry(frm01Red).grid(row=0,column=3,padx=6,pady=6)
tk.Button(frm01Red,text="更新").grid(row=0,column=4,padx=6,pady=6)

frm00Blue = tk.Frame(win, bg="blue",highlightthickness=2)
frm00Blue.grid(row=0,column=0,rowspan=2,sticky="NS")
tk.Label(frm00Blue,text="筛选条件:").grid(row=0,padx=6,pady=6,sticky="W")
tk.Checkbutton(frm00Blue,text="男性").grid(row=1,padx=6,pady=6)
tk.Checkbutton(frm00Blue,text="女性").grid(row=2,padx=6,pady=6)
tk.Checkbutton(frm00Blue,text="博士").grid(row=3,padx=6,pady=6)
tk.Label(frm00Blue,text="符合条件的名单:").grid(row=4,padx=6,sticky="W")
nameList = tk.Listbox(frm00Blue)
nameList.grid(row=5,padx=6,pady=6)
for x in ['张三','李四','王五','李丽','刘娟']:
nameList.insert(tk.END,x) #将x插入到列表框尾部。

frm21Green = tk.Frame(win,bg='green',highlightthickness=2)
frm21Green.grid(row=2,column=0,columnspan=2,sticky="WE")
tk.Label(frm21Green, text="提示:目前一切正常").grid(row=0,padx=6,pady=6)

frm11Yellow = tk.Frame(win, bg='yellow',highlightthickness=2)
frm11Yellow.grid(row=1,column=1,sticky="NSWE") #要贴住单元格四条边
frm11Yellow.rowconfigure(1,weight=1) #使得frm11Yellow中第1行高度会自动伸缩
frm11Yellow.columnconfigure(0,weight=1)
tk.Label(frm11Yellow,text="简历: ").grid(row=0,padx=6,pady=6,sticky="W")
tk.Text(frm11Yellow).grid(row=1,padx=15,pady=15,sticky="NSWE")
# sticky="NSWE"使得该多行编辑框会自动保持填满整个单元格

win.rowconfigure(1, weight=1)
win.columnconfigure(1, weight=1)
win.mainloop()

控件属性和事件响应

控件属性和事件响应概述

  • 有的控件有函数可以用来设置和获取其属性,或以字典下标的形式获取和设置其属性
1
2
3
4
5
lbHint = tk.Label(win, text="请登录")
lbHint["text"] = "登录成功!" #修改lbHint的文字

txt = tk.Text(win)
txt.get(0.0, tk.END)) #取全部文字
  • 有的控件必须和一个变量相关联,取变量值或设置变量值,就是取或设置该控件的属性
1
2
3
4
5
s = tk.StringVar()
s.set("sin(x)")

tk.Entry(win, textvariable=s)
print(s.get())
  • 创建有些控件时,可以用command参数指定控件的事件响应函数
1
2
tk.Button(win,text="显示函数图",command=myfunc) #myfunc是函数名
tk.Checkbox(win,text="显示函数图",command=lambda:print("hello"))
  • 可以用控件的bind函数指定事件响应函数
1
2
lb = tk.Label(win,text="something")
lb.bind("<ButtonPress-1>",mouse_down) #鼠标左键按下事件

基本的控件属性和事件响应示例

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
import tkinter as tk
def btLogin_click(): #登录按钮的事件响应函数,点击该按钮时被调用
if username.get()=="pku" and password.get()=="123": #正确的用户名和密码
lbHint["text"] = "登录成功!" #修改lbHint的文字
lbHint["fg"] = "black" #文字变成黑色, "fg"表示前景色,"bg"表示背景色
else:
username.set("") #将用户名输入框清空
password.set("") #将密码输入框清空
lbHint["fg"] = "red" #文字变成红色
lbHint["text"] = "用户名密码错误,请重新输入!"

def cbPassword_click(): #"显示密码"单选框的事件响应函数,点击该单选框时被调用
if showPassword.get():#showPassword是和cbPassword绑定的tkinter布尔变量
etPassword["show"] = "" #使得密码输入框能正常显示密码。 Entry有show属性
else:
etPassword["show"] = "*"

win = tk.Tk()
win.title("登录")

username,password = tk.StringVar(),tk.StringVar()
#两个字符串类型变量,分别用于关联用户名输入框和密码输入框
showPassword = tk.BooleanVar() #用于关联“显示密码”单选框
showPassword.set(True) #使得cbPassowrd开始就是选中状态

lbHint = tk.Label(win,text="请登录")
lbHint.grid(row=0,column=0,columnspan=2)
lbUsername = tk.Label(win,text="用户名: ")
lbUsername.grid(row=1,column=0,padx=5,pady=5)
lbPassword = tk.Label(win,text="密码: ")
lbPassword.grid(row=2,column=0,padx=5,pady=5)

etUsername = tk.Entry(win,textvariable=username)
#输入框etUsername和变量username关联
etUsername.grid(row=1,column=1,padx=5,pady=5)
etPassword = tk.Entry(win,textvariable=password,show="*")
#Entry的属性show="*"表示该输入框不论内容是啥,只显示'*'字符,为""则正常显示
etPassword.grid(row=2,column=1,padx=5,pady=5)
cbPassword = tk.Checkbutton(win,text="显示密码",variable=showPassword,command=cbPassword_click)
#cbPassword关联变量showPassword,其事件响应函数是cbPassword_click,即点击它时
#会调用 cbPassword_click()
cbPassword.grid(row=3,column=0,padx=5,pady=5)

btLogin = tk.Button(win,text="登录",command=btLogin_click)
#点击btLogin按钮会执行btLogin_click()
btLogin.grid(row=4,column=0,pady=5)

btQuit = tk.Button(win,text="退出",command=win.quit)
#点击btQuit会执行win.quit(),win.quit()导致窗口关闭,于是整个程序结束
btQuit.grid(row=4,column=1,pady=5)
win.mainloop()

Python火锅店实例

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
import tkinter as tk
from tkinter import ttk # ttk中有更多控件

gWin = None # 表示窗口
gDishes = (("清汤(20元)", "滋补(40元)", "鸳鸯(60元)"), # 锅底
("香菜(10元)", "麻酱(20元)", "韭花(20元)"), # 佐料
("羊肉(30元)", "肥牛(40元)", "白菜(10元)", "茼蒿(20元)")) # 菜品

def addToListbox(listbox, lst):
for x in lst:
listbox.insert(tk.END, x) # 将x添加到列表框尾部

def doDiscount():
gWin.discount = [1, 0.9, 0.8][gWin.custom.get()]
gWin.lbHint["text"] = "饭菜总价:" + str(int(gWin.totalCost * gWin.discount)) + "元"
gWin.lbHint["fg"] = "black"

def categoryChanged(event): # gWin.cbxCategory选项变化时被调用
gWin.lsbDishes.delete(0, tk.END) # 删除全部内容,delete(x,y)删除第x项到第y项
idx = gWin.cbxCategory.current() # gWin.cbxCategory当前选中的是第idx项
addToListbox(gWin.lsbDishes, gDishes[idx]) # 装入相应菜单
gWin.lsbDishes.select_set(0, 0)

def btAdd_click():
# btAdd["state"] = tk.DISABLED tk.NORMAL
sel = gWin.lsbDishes.curselection() # sel形如 (1,2,3)
if sel == ():
gWin.lbHint["text"] = "您还没有选中要添加的菜"
gWin.lbHint["fg"] = "red"
else:
dish = gWin.lsbDishes.get(sel)
price, num = int(dish[3:5]), gWin.dishNum.get()
gWin.lsbTable.insert(tk.END, "[" + gWin.category.get() + "]" + dish + " X" + num)
gWin.totalCost += price * int(num)
gWin.lbHint["text"] = "饭菜总价:" + str(int(gWin.totalCost * gWin.discount)) + "元"
gWin.lbHint["fg"] = "black"

def btDelete_click():
sel = gWin.lsbTable.curselection()
if sel == ():
gWin.lbHint["text"] = "您还没有选中要删除的菜"
gWin.lbHint["fg"] = "red"
else:
for i in sel:
dish = gWin.lsbTable.get(i)
price = int(dish[7:9])
price *= int(dish[dish.index("X") + 1:])
gWin.totalCost -= price
gWin.lbHint["text"] = "饭菜总价:" + str(int(gWin.totalCost * gWin.discount)) + "元"
gWin.lbHint["fg"] = "black"
for i in sel[::-1]: # 从后往前删除
gWin.lsbTable.delete(i)

def main():
global gWin
gWin = tk.Tk()
gWin.title("Python火锅店")
gWin.geometry("520x300")
gWin.totalCost, gWin.discount = 0, 1 # 总价和折扣
gWin.resizable(False, False) # gWin不可改变大小

lb = tk.Label(gWin, text="欢迎光临Python火锅店", bg="red", fg="white", font=('黑体', 20, 'bold'))
lb.grid(row=0, column=0, columnspan=4, sticky="EW")

gWin.category = tk.StringVar() # 对应组合框gWin.cbxCategory收起状态显示的文字
gWin.cbxCategory = ttk.Combobox(gWin, textvariable=gWin.category)
gWin.cbxCategory["values"] = ("锅底", "佐料", "菜品") # 下拉时显示的表象
gWin.cbxCategory["state"] = "readonly" # 将gWin.cbxCategory设置为不可输入,只能选择
gWin.cbxCategory.current(0) # 选中第0项
gWin.cbxCategory.grid(row=1, column=0, sticky="EW")

gWin.lsbDishes = tk.Listbox(gWin, selectmode=tk.SINGLE, exportselection=False) # exportselection使得列表框失去输入焦点也能保持选中项目
gWin.lsbDishes.bind("<Double-Button-1>", lambda e:btAdd_click())
gWin.lsbDishes.bind("<<ListboxSelect>>", lambda e:gWin.dishNum.set("1"))

addToListbox(gWin.lsbDishes, gDishes[0]) # 装入锅底菜单
gWin.lsbDishes.select_set(0, 0) # select_set(x,y)可以选中第x项到第y项(包括y)
gWin.lsbDishes.grid(row=2, column=0, sticky="EWNS")
gWin.cbxCategory.bind("<<ComboboxSelected>>", categoryChanged)
# 当组合框下拉后有表现被选中时,会发生ComboboxSelected事件。
# 此处指定该事件发生时,会调用gWin.categoryChanged函数
# 指定"<<ComboboxSelected>>"事件的响应函数是gWin.categoryChanged

gWin.lsbTable = tk.Listbox(gWin, selectmode=tk.EXTENDED, exportselection=False)
gWin.lsbTable.grid(row=2, column=2, sticky="EWNS")
tk.Label(gWin, text="我的餐桌").grid(row=1, column=2)
gWin.lbHint = tk.Label(gWin, text="饭菜总价:0元")
gWin.lbHint.grid(row=3, column=0, columnspan=3, sticky="W")

scrollbar = tk.Scrollbar(gWin, width=20, orient="vertical", command=gWin.lsbTable.yview)
gWin.lsbTable.configure(yscrollcommand=scrollbar.set) # 绑定listbox和scrollbar
scrollbar.grid(row=2, column=3, sticky="NS")

frm = tk.Frame(gWin)
frm.grid(row=2, column=1)
tk.Label(frm, text="数量:").grid(row=0, column=0)
gWin.dishNum = tk.StringVar(value="1")
gWin.spNum = tk.Spinbox(frm, width=5, from_=1, to=1000, textvariable=gWin.dishNum)
gWin.spNum.grid(row=0, column=1)

btAdd = tk.Button(frm, text="添加", command=btAdd_click)
btAdd.grid(row=1, column=0, columnspan=2, sticky="EW")
btDelete = tk.Button(frm, text="删除", command=btDelete_click)
btDelete.grid(row=2, column=0, columnspan=2, sticky="EW")

lbfDiscount = tk.LabelFrame(frm, text="价格")
lbfDiscount.grid(row=3, column=0, columnspan=2)
gWin.custom = tk.IntVar() # 如果写 gWin.custom = tk.IntVar(value=0)就不用下一行了
gWin.custom.set(0)
# 三个单选框绑定了一个,所以同时只可以选择一个
rb = tk.Radiobutton(lbfDiscount, text="普通价", value=0, variable=gWin.custom, command=doDiscount)
rb.grid(row=0, column=0, sticky="W")
rb = tk.Radiobutton(lbfDiscount, text="会员价(九折)", value=1, variable=gWin.custom, command=doDiscount)
rb.grid(row=1, column=0, sticky="W")
rb = tk.Radiobutton(lbfDiscount, text="VIP价(八折)", value=2, variable=gWin.custom, command=doDiscount)
rb.grid(row=2, column=0, sticky="W")

gWin.columnconfigure(0, weight=1)
gWin.columnconfigure(2, weight=1)
gWin.rowconfigure(2, weight=1)
gWin.mainloop()

main()

菜单和编辑框

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

gWin = None

def muCut_click():
gWin.txtFile.event_generate("<<Cut>>")

def muCopy_click():
gWin.txtFile.event_generate("<<Copy>>")

def muPaste_click():
gWin.txtFile.event_generate('<<Paste>>')

def saveTextFile(fileName):
f = open(fileName, "w", encoding="utf-8")
f.write(gWin.txtFile.get(0.0, tk.END))
f.close()

def muSaveAs_click():
fileName = filedialog.asksaveasfilename(title='Save File', initialdir='c:/tmp', initialfile='untitled.txt', filetypes=[('Text File', '*.txt')], defaultextension=".txt")
if fileName != "":
saveTextFile(fileName)
gWin.title(fileName)
gWin.curFileName = fileName

def muSave_click():
if gWin.curFileName.lower() == "untitled.txt":
muSaveAs_click()
else:
saveTextFile(gWin.curFileName)

def muOpen_click():
global gWin
fileName = filedialog.askopenfilename(title='Open File', filetypes=[('Text Files', '*.txt'), ('All Files', '*')])
if fileName != "":
gWin.curFileName = fileName
gWin.title(fileName)
f = open(fileName, "r")
text = f.read()
f.close()
gWin.txtFile.delete(0.0, tk.END)
gWin.txtFile.insert("insert", text) #gWin.txtFile.insert(tk.END, text)加在最后

def muBigFont_click():
if gWin.isBigFont.get() == 1:
gWin.txtFile.configure(font=("SimHei", 18, "bold"))
else:
gWin.txtFile.configure(font=("", 10))

def muNew_click():
global gWin
gWin.txtFile.delete(0.0, tk.END)
gWin.title("untitled.txt")
gWin.curFileName = "untitled.txt"

def muPrintSelection_click():
if gWin.txtFile.tag_ranges(tk.SEL):
print(gWin.txtFile.selection_get())

def main():
global gWin
gWin = tk.Tk()
gWin.title("untitled.txt")
gWin.menubar = tk.Menu(gWin)

gWin.fileMenu = tk.Menu(gWin.menubar, tearoff=0) # 去掉顶端横线
gWin.menubar.add_cascade(label='File', menu=gWin.fileMenu) # 添加一个子菜单 File
gWin.fileMenu.add_command(label='New', command=muNew_click)
gWin.fileMenu.add_command(label='Open', command=muOpen_click)
gWin.fileMenu.add_command(label='Save', command=muSave_click, accelerator="Ctrl+S")
gWin.fileMenu.add_command(label='Save As', command=muSaveAs_click)
gWin.fileMenu.add_separator() # 加分割线
gWin.fileMenu.add_command(label='Exit', command=gWin.quit)

editMenu = tk.Menu(gWin.menubar, tearoff=0)
gWin.menubar.add_cascade(label='Edit', menu=editMenu)
editMenu.add_command(label='Cut', command=muCut_click)
editMenu.add_command(label='Copy', command=muCopy_click)
editMenu.add_command(label='Paste', command=muPaste_click)

settingsMenu = tk.Menu(editMenu, tearoff=0)
editMenu.add_cascade(label='Settings', menu=settingsMenu)
gWin.isBigFont = tk.IntVar()
settingsMenu.add_checkbutton(label="Big Font", command=muBigFont_click, variable=gWin.isBigFont)
settingsMenu.add_command(label="Print Selection", command=muPrintSelection_click)

gWin.config(menu=gWin.menubar)
gWin.txtFile = tk.Text(gWin)
gWin.txtFile.grid(row=0, column=0, sticky="NWSE")
gWin.curFileName = "untitled.txt"

gWin.rowconfigure(0, weight=1)
gWin.columnconfigure(0, weight=1)
gWin.bind_all("<Control-s>", lambda event:muSave_click())
gWin.mainloop()
main()

对话框

  • 作用:弹出小窗口和用户进行交互,或者显示信息

文件对话框

自定义对话框

  • 用于复杂一点的交互
  • 自定义对话框要点
    1) 自定义对话框是一个 Toplevel窗口,控件布局方式、事件响应方式和普通窗口一样
1
2
dialog = tk.Toplevel(gWin) #gWin是主窗口,创建对话框窗口
dialog.grab_set() #显示对话框,并独占输入焦点

2) 关闭自定义对话框:

1
dialog.destroy()

对话框整合示例

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
import tkinter as tk
from tkinter import messagebox
from tkinter import simpledialog
from tkinter import filedialog
gWin = None

def cmd(n):
def innerCmd(): #innerCmd是个闭包
# if n <= 4:
# func = eval("messagebox." + gWin.titles[n])
# value = func("Dialog", gWin.titles[n])
# elif n <= 7:
# func = eval("simpledialog." + gWin.titles[n])
# value = func("Dialog", gWin.titles[n])
if n == 0: value = messagebox.askokcancel("Dialog", gWin.titles[n])
elif n == 1: value = messagebox.askyesno("Dialog", gWin.titles[n])
elif n == 2: value = messagebox.showerror("Dialog","抱歉,您的账户余额不足!")
elif n == 3: value = messagebox.showinfo("Dialog", gWin.titles[n])
elif n == 4: value = messagebox.showwarning("Dialog",gWin.titles[n])
elif n == 5: value = simpledialog.askfloat("Dialog","请输入支付金额:")
elif n == 6: value = simpledialog.askinteger("Dialog",gWin.titles[n])
elif n == 7: value = simpledialog.askstring("Dialog", gWin.titles[n])
elif n == 8: value = filedialog.askopenfilename(title='打开文件', filetypes=[('images', '*.jpg *.png'), ('text','*.txt'),('All Files', '*')])
elif n == 9: value = filedialog.asksaveasfilename(title='保存文件', initialdir='c:/tmp', initialfile='hello.py')
elif n == 10: value = filedialog.askopenfilenames(title='打开文件', filetypes=[('images', '*.jpg *.png'), ('All Files', '*')])
elif n == 11: value = filedialog.askdirectory(title='打开文件', initialdir='c:/tmp2')
print(n,value,type(value))
return innerCmd

def main():
global gWin
gWin = tk.Tk()
gWin.titles = ["askokcancel", "askyesno", "showerror",
"showinfo", "showwarning", "askfloat", "askinteger",
"askstring", "askopenfilename", "asksaveasfilename",
"askopenfilenames", "askdirectory"]
for i in range(12):
button = tk.Button(gWin, text = gWin.titles[i], command=cmd(i))
button.grid(row=i//4, column=i%4, padx=5, pady=5)
gWin.columnconfigure(0,weight=1)
gWin.mainloop()
main()

显示图像和matplotlib绘图

  • tkinter界面上matplotlib绘图要点

1) 创建一个matplotlib.pyplot.Figure对象fig
2) 在fig对象上用add_subplot()创建一个子图ax
3) canvas = FigureCanvasTkAgg(fig, master=win)得到一个将fig绑定在win上面的FigureCanvasTkAgg对象canvas。win可以是窗口,也可以是Frame,LabelFrame
4) canvas.get_tk_widget().grid(....)将canvas布局到win的合适位置
5) 子图ax上画完图后,还要cavans.draw()才能刷新显示

  • 用Label显示图像要点

1) aLabel.config(image = tkinter.PhotoImage(file='xxx.gif')) 可以显示gif图像
2) 要显示jpg,png需要用到PIL库里的Image和ImageTk

1
2
3
from PIL import Image,ImageTk
img = ImageTk.PhotoImage(Image.open("XXX.jpg"))
aLabel.config(image=img)

注意,可能是库有bug,此处img必须不是局部变量,否则可能无法显示图像。

  • 两个组件重叠,只显示一个的要点

两个组件可以放在同一个单元格里面。

ctrl.grid_forget() 可以让ctrl组件消失
ctrl.grid(....)又将其恢复

  • 鼠标移动显示位置

为Label添加鼠标左键按下、松开和鼠标移动三个事件响应函数

1
2
3
lbImg.bind("<Motion>", mouse_move)
lbImg.bind("<ButtonPress-1>", mouse_down)
lbImg.bind("<ButtonRelease-1>", mouse_up)
  • 示例
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
import numpy as np
from PIL import Image,ImageTk
from math import *
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
from tkinter import filedialog

gWin = None

def mouse_down(event):
gWin.mouseDown = True

def mouse_up(event):
gWin.mouseDown = False
gWin.lbMsg["text"] = "按住鼠标键移动,会显示鼠标位置"

def mouse_move(event):
if gWin.mouseDown:
gWin.lbMsg["text"] = "鼠标位置:(%d,%d)" % (event.x,event.y)

def showImage():
fileName = filedialog.askopenfilename(title='打开文件', initialdir="c:/tmp2/", filetypes=[('images', '*.jpg *.png')])
if fileName != "":
gWin.geometry("")
gWin.frmPlot.grid_forget()
gWin.frmImg.grid(row=1,column=0,sticky="ESWN")
gWin.img = ImageTk.PhotoImage(Image.open(fileName)) # 用PIL模块的PhotoImage打开
# and keep a reference。如果 仅img = ImageTk.....然后 lbImage.config(image=img),则显示不出来
gWin.lbImg.config(image=gWin.img)
def showPlot():
gWin.geometry("")
gWin.frmImg.grid_forget()
gWin.frmPlot.grid(row=1,column=0,sticky="ESWN")
gWin.ax.clear() # gWin.fig.clear()
xs = np.linspace(-3, 3, 100)
y = [eval(gWin.fstr.get()) for x in xs]
gWin.ax.plot(xs,y,color='red',linewidth=1.0,linestyle='--')
gWin.canvas.draw()
def main():
global gWin
gWin = tk.Tk()
frm = tk.Frame(gWin)
frm.grid(row=0,column=0,sticky="EW")
tk.Label(frm,text="y =").grid(row=0,column=0,padx=5,pady=5)
gWin.fstr = tk.StringVar()
gWin.fstr.set("sin(x)")
tk.Entry(frm,textvariable = gWin.fstr).grid(row=0,column=1,padx=5,pady=5)
tk.Button(frm,text="显示函数图",command=showPlot).grid(row=0,column=2,padx=5,pady=5)
tk.Button(frm, text="显示图像文件", command=showImage).grid(row=0, column=3, padx=5, pady=5)
gWin.frmImg = tk.Frame(gWin)
gWin.lbImg = tk.Label(gWin.frmImg)
gWin.lbImg.grid(row=0, column=0, sticky="NSWE")
gWin.lbMsg = tk.Label(gWin.frmImg, fg="white", bg="red", text= "按住鼠标键移动,会显示鼠标位置")
gWin.lbMsg.grid(row=1, column=0, sticky="EW")
gWin.mouseDown = False
gWin.lbImg.bind("<Motion>", mouse_move)
gWin.lbImg.bind("<ButtonPress-1>",mouse_down)
gWin.lbImg.bind("<ButtonRelease-1>", mouse_up)
gWin.frmPlot = tk.Frame(gWin)
gWin.fig = plt.Figure(figsize=(5, 4), dpi=100)
gWin.ax = gWin.fig.add_subplot()
gWin.canvas = FigureCanvasTkAgg(gWin.fig, master=gWin.frmPlot)
gWin.canvas.get_tk_widget().grid(row=0, column=0,sticky="ESNW")
gWin.frmPlot.grid(row=1,column=0,sticky="ESWN")
showPlot()
gWin.rowconfigure(1,weight = 1)
gWin.columnconfigure(0, weight=1)
gWin.mainloop()

main()
文章作者: HibisciDai
文章链接: http://hibiscidai.com/2023/02/04/实用Python程序设计MOOC-第十四章tkinter图形界面程序设计/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 HibisciDai
好用、实惠、稳定的梯子,点击这里