Error message here!

Hide Error message here!

忘记密码?

Error message here!

请输入正确邮箱

Hide Error message here!

密码丢失?请输入您的电子邮件地址。您将收到一个重设密码链接。

Error message here!

返回登录

Close

Python GUI编程:tkinter简介

TurboBoost 2021-04-21 11:52:44 阅读数:1 评论数:0 点赞数:0 收藏数:0

本文内容来自我的笔记。撰写过程中参考了胡俊峰老师《Python程序设计与数据科学导论》课程的内容。

本文包含大量代码,阅读时请自行运行以观察效果。

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)。

在每一个组件中,可以选择三种布局方案之一(不能用超过一种)来排列其儿子(管不到孙子):packgridplace

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()
fillexpand参数
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参数在PackPlace中也可以使用。

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所处的坐标系)的原点永远是父组件的左上角,而不是窗口的左上角(如果父组件不是窗口的话)。xrelx的区别仅在于步长,而不在于坐标原点。

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挤占空间,也至少会留出后代组件的空间。

事件绑定

事件绑定,即让特定事件来触发特定函数的执行。Buttoncommand参数,是事件绑定的一个特例;更一般的绑定由组件对象的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>

更多内容见:

版权声明
本文为[TurboBoost]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/turboboost/p/tkinter.html

编程之旅,人生之路,不止于编程,还有诗和远方。
阅代码原理,看框架知识,学企业实践;
赏诗词,读日记,踏人生之路,观世界之行;