例程合集:Python tkinter 包的使用
本文内容来自我的笔记。撰写过程中参考了胡俊峰老师《Python程序设计与数据科学导论》课程的内容。
本文中包含若干个例程,力图囊括 tkinter 的基本使用方法。请自行运行例程以观察效果。
本文中只会给出例程和简要的说明,而不会作详细的解释。因此它不是一篇教程,可能不适合新手阅读。
GUI编程:Tkinter包
窗口
import tkinter as tk
import time
window = tk.Tk() # new window
window.title('Hello') # window title
window.geometry('350x200') # window size; can be changed by mouse dragging
print('press enter to show the window...')
input()
window.mainloop() # show window, start main loop.
# the main loop keeps monitoring the user's operations on the window, and make responses correspondingly.
print('window closed.')
press enter to show the window...
window closed.
# 请先阅读“常用组件”和“布局方案”再来阅读本例
import tkinter as tk
import time
window = tk.Tk()
window.title('Hello')
window.geometry('350x500')
def clicked1():
tk.Label(window, text='A clicked').pack()
window.update() # mainloop会时不时刷新每一个窗口,但我不清楚其频率。保险起见这里手动刷新。
def clicked2():
new_window = tk.Tk()
new_window.geometry('200x150')
tk.Label(new_window, text='B clicked').pack()
# mainloop是所有窗口共用的。现在mainloop已经被window开启了,new_window就不需要再次开启,只要静静等待mainloop刷新出自己就行。
# 如果不想等待的话,运行一下new_window.update()也可以
new_window.after(1000, new_window.destroy) # 告诉新窗口,在1000ms后关闭自己。`after`方法不会阻塞;新窗口记住这件事之后就会转身去干别的。
# `new_window.destroy()`的作用是关闭窗口。(类似地,组件也有destroy方法)
# 这里不能用time.sleep,因为在sleep过程中mainloop会中断,此期间点A会没反应,新窗口也来不及被显示出来
button1 = tk.Button(window, text='A', font=('Courier New', 20), bg='blue', command=clicked1)
button1.pack()
button2 = tk.Button(window, text='B', font=('Courier New', 20), bg='red', command=clicked2)
button2.pack()
window.mainloop()
import tkinter as tk
import time
window = tk.Tk()
window.title('Hello')
window.geometry('350x500')
def my_update():
print('update!')
window.after(1000, my_update)
window.after(1000, my_update) # 每秒刷新一次
window.mainloop()
常用组件
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue') # a text label on `window`
label1.grid(column=0, row=0) # position the label at (0,0); the label won't show without this command
# the grid is dynamic; width of columns and height of rows is determined by the contents.
label2 = tk.Label(window, text='M', font=('Courier New', 20))
label2.grid(column=0, row=1)
label3 = tk.Label(window, text='G', font=('Courier New', 20))
label3.grid(column=1, row=0)
window.mainloop()
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
def clicked():
button.configure(text='H', bg='white')
button = tk.Button(window, text='L', font=('Courier New', 20), bg='grey', fg='blue', command=clicked)
button.grid(column=0, row=0)
window.mainloop()
import tkinter as tk
from tkinter import messagebox
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
def clicked():
tk.messagebox.showinfo('Don\'t move', 'Raise your hands!', parent=window)
button = tk.Button(window, text='L', font=('Courier New', 20), bg='grey', fg='blue', command=clicked)
button.grid(column=0, row=0)
window.mainloop()
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label = tk.Label(window, text='[EMPTY]', font=('Courier New', 10))
label.grid(column=0, row=1)
txt = tk.Entry(window, width=10) # a one-line input box; width in px
txt.grid(column=0, row=0)
txt.focus() # put the focus on the input box as soon as the window gets focus;
# so that you don't need to click on the box and then start inputting.
def clicked():
label.configure(text=txt.get())
button = tk.Button(window, text='Submit', font=('Courier New', 10), bg='grey', fg='blue', command=clicked)
button.grid(column=1, row=0)
window.mainloop()
import tkinter as tk
from tkinter import ttk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label = tk.Label(window, text='[EMPTY]', font=('Courier New', 10))
label.grid(column=0, row=1)
combo = ttk.Combobox(window)
combo.grid(column=0, row=0)
combo['values'] = ('abc','de',123)
combo.current(1) # default is 'de'
def clicked():
label.configure(text=combo.get())
button = tk.Button(window, text='Submit', font=('Courier New', 10), bg='grey', fg='blue', command=clicked)
button.grid(column=1, row=0)
window.mainloop()
其他组件还包括:
Checkbutton
: 多选框Radiobutton
: 单选框ScrolledText
: 多行文本框Spinbox
: 带上下按钮的数值输入框Progressbar
: 进度条filedialog
: “打开文件”对话框
布局方案
TKinter中组件之间的嵌套关系是树状的(有点像HTML的tag),其中窗口是树根,非叶节点往往是Frame
组件——一种不会显示出来的占位组件(有点像HTML的span)。
在每一个组件中,可以选择三种布局方案之一(不能用超过一种)来排列其儿子(管不到孙子):pack
、grid
和place
。
Pack
方案
Pack
方案的大致思路就是将儿子简单地堆叠。
side
参数
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue') # a text label on `window`
label1.pack() # by default side='top'
label2 = tk.Label(window, text='M', font=('Courier New', 20))
label2.pack()
label3 = tk.Label(window, text='G', font=('Courier New', 20))
label3.pack()
window.mainloop()
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue') # a text label on `window`
label1.pack(side='right')
label2 = tk.Label(window, text='M', font=('Courier New', 20))
label2.pack(side='right')
label3 = tk.Label(window, text='G', font=('Courier New', 20))
label3.pack(side='right')
window.mainloop()
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue') # a text label on `window`
label1.pack(side='top')
label2 = tk.Label(window, text='M', font=('Courier New', 20))
label2.pack(side='bottom')
label3 = tk.Label(window, text='G', font=('Courier New', 20))
label3.pack(side='bottom')
window.mainloop()
从上面几例可以看到,pack()
会优先满足 代码中靠前的组件 的side
志愿。
anchor
参数及其与side
的联合使用
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue') # a text label on `window`
label1.pack(anchor='nw') # northwest; similarly there're n,e,w,s,ne,se,sw,center
label2 = tk.Label(window, text='M', font=('Courier New', 20), bg='blue')
label2.pack(anchor='center')
label3 = tk.Label(window, text='G', font=('Courier New', 20), bg='red')
label3.pack(anchor='se')
window.mainloop()
为什么三个字在垂直方向上不是分布在上、中、下,而是挤在窗口的上半部?
因为还有一个缺省的side=top
,这使得每个字在 被定位到anchor
所指定的位置 之后,还向会上坠落。看一下下两个例子,你可能会更清楚一点。
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue') # a text label on `window`
label1.pack(anchor='nw', side='bottom') # northwest; similarly there're n,e,w,s,ne,se,sw,center
label2 = tk.Label(window, text='M', font=('Courier New', 20), bg='blue')
label2.pack(anchor='center', side='bottom')
label3 = tk.Label(window, text='G', font=('Courier New', 20), bg='red')
label3.pack(anchor='se', side='bottom')
window.mainloop()
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue') # a text label on `window`
label1.pack(anchor='n',side='right') # it starts at position `anchor` and then falls towards direction `side`
label2 = tk.Label(window, text='M', font=('Courier New', 20), bg='blue')
label2.pack(anchor='center',side='right')
label3 = tk.Label(window, text='G', font=('Courier New', 20), bg='red')
label3.pack(anchor='s',side='right')
window.mainloop()
fill
和expand
参数
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue') # a text label on `window`
label1.pack(side='left',fill='y') # "fill=y" means that it will stretch vertically; but won't exceed the space assigned to it by pack
label2 = tk.Label(window, text='M', font=('Courier New', 20), bg='blue')
label2.pack(side='left',fill='y')
label3 = tk.Label(window, text='G', font=('Courier New', 20), bg='grey')
label3.pack(side='left',fill='y')
window.mainloop()
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue') # a text label on `window`
label1.pack(side='top',fill='x') # stretch horizontally; won't exceed
label2 = tk.Label(window, text='M', font=('Courier New', 20), bg='blue')
label2.pack(side='top',fill='both') # stretch horizontally AND vertically; won't exceed; here it's no different from 'x'
label3 = tk.Label(window, text='G', font=('Courier New', 20), bg='grey')
label3.pack(side='top',fill='both')
window.mainloop()
从上面几例可以看到,虽然fill
参数会使得组件占满自己被分配的空间,但是pack
不会因为fill
参数而给一个组件分配更多空间。
与之相对地,expand
参数会要求pack
尽可能多地给一个组件分配空间。见下面的例子。
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue') # a text label on `window`
label1.pack(side='top',fill='both',expand=True)
label2 = tk.Label(window, text='M', font=('Courier New', 20), bg='blue')
label2.pack(side='top',fill='both',expand=True)
label3 = tk.Label(window, text='G', font=('Courier New', 20), bg='grey')
label3.pack(side='top',fill='both',expand=True)
window.mainloop()
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue') # a text label on `window`
label1.pack(side='top',fill='both',expand=True)
label2 = tk.Label(window, text='M', font=('Courier New', 20), bg='blue')
label2.pack(side='top',fill='both')
label3 = tk.Label(window, text='G', font=('Courier New', 20), bg='grey')
label3.pack(side='top',fill='both',expand=True)
window.mainloop()
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue') # a text label on `window`
label1.pack(side='top',expand=True)
label2 = tk.Label(window, text='M', font=('Courier New', 20), bg='blue')
label2.pack(side='top',expand=True)
label3 = tk.Label(window, text='G', font=('Courier New', 20), bg='grey')
label3.pack(side='top',expand=True)
window.mainloop()
Grid
方案
注:当父组件不是窗口(即根结点)时,Grid
会在父组件内部重新划出一个网格,而不会使用全窗口的网格。
注:不同的行的高度可以不相等;列同理。
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue')
label1.grid(column=0, row=0)
label2 = tk.Label(window, text='M', font=('Courier New', 20), bg='blue')
label2.grid(column=0, row=1)
label3 = tk.Label(window, text='G', font=('Courier New', 20), bg='grey')
label3.grid(column=1, row=0, rowspan=2) # won't fill the entire 2x1 space
window.mainloop()
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue')
label1.grid(column=0, row=0)
label2 = tk.Label(window, text='M', font=('Courier New', 20), bg='blue')
label2.grid(column=0, row=1, ipadx=5, ipady=10) # x_inner_padding=5, y_inner_padding=10
label3 = tk.Label(window, text='G', font=('Courier New', 20), bg='grey')
label3.grid(column=1, row=0, rowspan=2) # won't fill the entire 2x1 space
window.mainloop()
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue')
label1.grid(column=0, row=0)
label2 = tk.Label(window, text='M', font=('Courier New', 20), bg='blue')
label2.grid(column=0, row=1, padx=5, pady=10) # x_outer_padding=5, y_outer_padding=10 ("margin" in css)
label3 = tk.Label(window, text='G', font=('Courier New', 20), bg='grey')
label3.grid(column=1, row=0, rowspan=2) # won't fill the entire 2x1 space
window.mainloop()
padx/pady/ipadx/ipady
参数在Pack
和Place
中也可以使用。
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue')
label1.grid(column=0, row=0)
label2 = tk.Label(window, text='M', font=('Courier New', 20), bg='blue')
label2.grid(column=0, row=1, padx=5, pady=10) # x_outer_padding=5, y_outer_padding=10 ("margin" in css)
label3 = tk.Label(window, text='G', font=('Courier New', 20), bg='grey')
label3.grid(column=1, row=0, rowspan=2, sticky='s') # `sticky` is similar to `anchor` in Pack (with the same list of possible values),
# but `sticky` determines the label's position **inside a cell**
window.mainloop()
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue')
label1.grid(column=0, row=0)
label2 = tk.Label(window, text='M', font=('Courier New', 20), bg='blue')
label2.grid(column=0, row=1, padx=5, pady=10) # x_outer_padding=5, y_outer_padding=10 ("margin" in css)
label3 = tk.Label(window, text='G', font=('Courier New', 20), bg='grey')
label3.grid(column=1, row=0, rowspan=2, sticky='nsew') # same as `fill=both` in Pack. similarly, `sticky=ns` and `sticky=ew`.
window.mainloop()
Place
方案
注:Place
中坐标系(包括参数x,y
所处的坐标系、和relx,rely
所处的坐标系)的原点永远是父组件的左上角,而不是窗口的左上角(如果父组件不是窗口的话)。x
与relx
的区别仅在于步长,而不在于坐标原点。
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue')
label1.place(x=100, y=50, width=50, height=100) # x in [0,width_of_father_in_px], y in [0,height_of_father_in_px]
label2 = tk.Label(window, text='M', font=('Courier New', 20), bg='blue')
label2.place(x=50, y=100, width=100, height=50)
window.mainloop()
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='L', font=('Courier New', 20), bg='grey', fg='blue')
label1.place(relx=0.4, rely=0.2, relwidth=0.2, relheight=0.4) # relx in [0,1], rely in [0,1]
# relx=1 <=> x=width_of_father_in_px
label2 = tk.Label(window, text='M', font=('Courier New', 20), bg='blue')
label2.place(relx=0.2, rely=0.4, relwidth=0.4, relheight=0.2)
window.mainloop()
联合使用
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
frame = tk.Frame(window, bg='black') # bg can be omitted (and it'll be transparent)
frame.place(x=50,y=50,width=100,height=100)
label1 = tk.Label(frame, text='L', font=('Courier New', 20), bg='grey', fg='blue') # a label whose father is `frame`
label1.pack(side='left')
label2 = tk.Label(frame, text='G', font=('Courier New', 20), bg='red')
label2.pack(anchor='se', side='right')
frame2 = tk.Frame(frame, bg='white')
frame2.pack(anchor='n', fill='both', expand=True)
label3 = tk.Label(frame2, text='M', font=('Courier New', 20), bg='blue')
label3.pack(fill='both', expand=True)
window.mainloop()
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
frame0 = tk.Frame(window, bg='green')
frame0.pack(fill='both', expand=True)
frame = tk.Frame(window, bg='black') # bg can be omitted (and it'll be transparent)
frame.pack()
# the following code is the same as previous cell
label1 = tk.Label(frame, text='L', font=('Courier New', 20), bg='grey', fg='blue') # a label whose father is `frame`
label1.pack(side='left')
label2 = tk.Label(frame, text='G', font=('Courier New', 20), bg='red')
label2.pack(anchor='se', side='right')
frame2 = tk.Frame(frame, bg='white')
frame2.pack(anchor='n', fill='both', expand=True)
label3 = tk.Label(frame2, text='M', font=('Courier New', 20), bg='blue')
label3.pack(fill='both', expand=True)
window.mainloop()
从上例可以看到,一个frame就算被别的(expand=True
的)frame挤占空间,也至少会留出后代组件的空间。
事件绑定
事件绑定,即让特定事件来触发特定函数的执行。Button
的command
参数,是事件绑定的一个特例;更一般的绑定由组件对象的bind
方法完成。
import tkinter as tk
window = tk.Tk()
window.title('Hello')
window.geometry('350x200')
label1 = tk.Label(window, text='A', font=('Courier New', 20), bg='grey', fg='blue')
label1.pack()
label2 = tk.Label(window, text='U', font=('Courier New', 20))
label2.pack()
def enter(event):
print(event)
label1.configure(text='fuck')
def leave(event):
print(event)
label1.configure(text='A')
label2.bind('<Enter>', enter)
label2.bind('<Leave>', leave)
window.mainloop()
<Enter event focus=False x=21 y=0>
<Leave event focus=False x=19 y=-3>
<Enter event focus=False x=4 y=29>
<Leave event focus=False x=25 y=19>
更多内容见: