# import tkinter
:Python 的标准库,用于制作 GUI 软件。
- 官方文档 (opens new window)
- 优点:
- 兼容类 Unix 系统、Windows 系统,显示经典桌面风格的 GUI 界面。
- 缺点:
- 功能简单,不如 PyQt 。
# 原理
tkinter 的全名为 tk interface ,是让 Python 调用 tk 的 API 。
- Tcl 是一种脚本语言,于 1988 年发布。
- tk 是一个 GUI 工具包。
- 于 1991 年发布,基于 Tcl 语言开发。后来移植到 Python、Perl 等编程语言。
- 调用 tk 的 API ,就可以在电脑屏幕上显示窗口、按钮等元素。
基于 tkinter 编写一个 Python 脚本之后,启动该 Python 脚本,就能显示 GUI 界面。
- Python 脚本需要使用 Python 解释器才能启动,而用户电脑不一定安装了 Python 解释器。因此建议用 pyinstaller ,将 Python 脚本打包成可执行文件,鼠标双击就能启动。
tkinter 可以显示 Window、Frame、Label、Button 等多种类型的 GUI 元素,它们统称为控件(widget)。
- 大部分 widget 之间存在父子关系。例如一个 Frame 可能包含多个 Label ,担任它们的父容器(parent)。
- 当父控件被销毁时,它的所有子控件都会被自动销毁。
- 一个 GUI 软件可能显示多个窗口。通常将第一个显示的窗口,称为主窗口。主窗口通常包含很多子控件。
# 启动
执行以下代码,即可显示一个 GUI 窗口:
import tkinter root = tkinter.Tk() # 这是创建一个 tk 对象,代表一个 GUI 窗口 # 如果在 Python 交互式终端执行 tkinter.Tk() ,则会立即显示窗口 # 如果在 Python 脚本中执行 tkinter.Tk() ,则等到执行 root.mainloop() 才会显示窗口 root.mainloop() # 这是进入主循环,阻塞当前线程,监听鼠标点击、键盘输入等 event ,并相应地刷新显示的内容 # root.mainloop() 只能在创建 root 的那个线程中调用。如果在其它线程中调用,则会导致程序崩溃退出 # 运行 mainloop() 时,如果抛出 Python 异常,则会将报错信息,打印在 Python 终端。如果抛出严重的异常,则可能导致程序崩溃退出
配置窗口:
root.title('main window') # 设置窗口的标题 root.geometry('500x300+0+0') # 设置窗口的尺寸和坐标,格式为 width x height + x坐标 + y坐标 root.resizable(width=True, height=True) # 设置宽度、高度是否可变 root.minsize(500, 300) # 设置窗口的最小尺寸
root.attributes('-topmost', True, # 使窗口置顶,位于本机所有窗口的上层 '-fullscreen', True, # 使窗口显示成全屏。此时不会显示窗口的边框、标题栏、关闭按钮 '-disabled', True, # 使窗口无法操作。此时窗口不会接收用户的鼠标点击、键盘输入,按 alt+F4 也不能关闭它 '-toolwindow', True, # 使窗口变为 toolwindow 类型,不会在电脑任务栏显示其图标 '-alpha', 0.9, # 设置窗口的透明度,0 代表完全透明,1 代表完全不透明 )
- 例如做一个恶作剧的窗口:同时设置 topmost、fullscreen、disable、toolwindow ,将电脑屏幕完全覆盖,导致用户不能操作电脑的其它软件。
- 解决方法:按 Windows+Tab 快捷键,新建一个 Windows 桌面,然后打开任务栏管理器,关闭上述窗口。
- 例如做一个恶作剧的窗口:同时设置 topmost、fullscreen、disable、toolwindow ,将电脑屏幕完全覆盖,导致用户不能操作电脑的其它软件。
# widget
# 布局
如何显示一个 widget ?
- 首先,需要创建一个 widget 。如果未指定其父容器,则默认采用 root 窗口作为父容器。
- 每个 widget 在创建之后,默认不会显示。需要调用一个布局方法,才会放到窗口中显示。例如:
tkinter.Label(root, text='hello').pack() # 可以在创建 widget 的同时,进行布局
- 如果想停止显示一个窗口、控件,可调用其
destroy()
方法销毁它,这会同时销毁以它为父容器的所有控件。例如:root.destroy()
- 多个 widget 可以叠加显示在同一位置,最晚显示的那个 widget 会显示在最上层。
每个 widget 都支持三种布局方法:
- pack()
widget.pack() # 将 widget 添加到窗口中,让它自动调整位置 widget.pack(side='top') # 将 widget 放在窗口的某一侧,可选 top、bottom、left、right widget.pack(fill='y') # 将 widget 在某一方向填满窗口,可选 none、x、y、both
- place()
widget.place(width=10, height=20, x=10, y=20) # 设置 widget 在父容器中的宽度、高度、坐标 widget.place(relwidth=0.5, relheight=0.5, relx=0.2, rely=0.2) # 相对与父容器的宽度、高度、坐标
- grid()
widget.grid(row=0, column=0, # 以网格的方式划分父容器的空间,将 widget 放在第 0 行、第 0 列 rowspan=1, # widget 占据几行,默认为 1 columnspan=1, # widget 占据几列,默认为 1 )
for row in range(3): for column in range(3): tkinter.Label(root, text='hello').grid(row=row,column=column)
- 如果将一个 widget 放在某行、某列,但是前面的一行、一列为空,不存在 widget ,则当前 widget 的坐标会自动向前漂移,从而填补空缺。
- 可随时调用某种布局方法,修改 widget 的位置。但是调用另一种布局方法时,可能毁掉之前的布局。
- pack()
# Label
- :标签。用于显示文本或图片。
- 例:
label1 = tkinter.Label(master=root, # 父容器 text='hello', # 文本内容 width=10, # 宽度 height=5 # 高度 bd=10, # 边框线条的宽度 font=('Arial', 12), # 字体 fg='#FFF', # 字体颜色。用三个十六进制数,或已有的颜色名,选择一种颜色 bg='#000', # 背景色。默认为透明 ) label1.pack() label1['text'] = 'world' # 修改显示的文本
- Label 可以显示 PNG、GIF 等格式的图片:
img = tkinter.PhotoImage(file='./1.png', width=200, height=100) tkinter.Label(root, image=img).pack()
# Frame
- :框架。用于包含一堆 widget ,担任它们的父容器。
- 例:
frame1 = tkinter.Frame(master=root) frame.pack() tkinter.Label(frame1, text='one').pack() tkinter.Label(frame1, text='two').pack()
# Button
- :按钮。
- 例:
button1 = tkinter.Button(master=root, text='close', command=root.destroy) button1.pack()
- 创建 button 时,可以将一个函数,赋值给 command 参数。当 button 被触发时(即鼠标左键按下再松开),就会执行该函数。
# Checkbutton
- :复选按钮。
- 例:
check1_var = tkinter.IntVar() # 创建一个变量,用于存储复选按钮的状态 check1 = tkinter.Checkbutton(root, text= 'test', variable=check1_var) check1.pack() check1_var.get() # 读取变量的值。取值为 1 或 0 ,表示复选按钮是否被勾选 check1.select() # 设置为选中状态 check1.deselect() # 设置为不选中状态 check1.toggle() # 在选中状态,与不选中状态之间,进行切换 check1.invoke() # 调用此方法,会模拟用户点击一次复选按钮
# Entry
- :输入框。供用户输入一行文本。
- 例:
entry1_var = tkinter.Variable() # 创建一个变量,用于存储输入框的文本内容 entry1 = tkinter.Entry(root, textvariable=entry1_var) entry1.pack() button1 = tkinter.Button(root, text='Entry', command=lambda : print(entry1_var.get())) button1.pack()
# Text
- :文本框。供用户输入多行文本,可以插入文本、图片、按钮。
- 例:
text1 = tkinter.Text(root) text1.pack() text1.config(state='disabled') # 使文本框不可编辑。用户、程序都不能编辑它 text1.config(state='normal') # 使文本框允许编辑 # 插入文本 text1.insert('1.0', 'Hello\n') # 在指定位置,插入一个字符串。这个位置可以是第几行的第几个字符,或者已命名的特定位置 text1.delete('1.2','end') # 删除从第 1 行的第 2 个字符,到文本末尾,之间的内容 # 插入图片 img = tkinter.PhotoImage(file='./1.png') text1.image_create('end', image=img) # 插入按钮 button1 = tkinter.Button(root, text='UP') text1.window_create('end', window=button1)
# messagebox
- :消息框。
- 消息框会作为一个独立的窗口进行显示,不必设置父容器。
- 同时最多显示一个消息框,关闭一个消息框之后,才会显示下一个消息框。
- 例:
from tkinter import messagebox # 显示一个消息框,默认只有一个 "确定" 按钮 # 消息框有 info、warning、error 三种类型,显示的图标不同 messagebox.showinfo(title='OK', message='xxx') messagebox.showwarning(title='warning', message='xxx') messagebox.showerror(title='error', message='xxx') # 显示一个询问框,有 "确定"、"取消" 两个按钮,被用户点击之后, askokcancel() 会返回 True 或 False if messagebox.askokcancel('ask', '确定or取消'): messagebox.showinfo(message='已确定') else: messagebox.showinfo(message='已取消') # 显示一个询问框,有 "是"、"否" 两个按钮,被用户点击之后,会返回 True、False messagebox.askyesno('ask','是or否') # 显示一个询问框,有 "是"、"否"、"取消" 三个按钮,被用户点击之后,会返回 True、False、None messagebox.askyesnocancel('ask','是or否or取消')
# event
用户对 widget 进行的鼠标点击、键盘输入等操作,统称为事件(event)。
每个 widget 可以用
bind()
方法,将某种 event 与某个函数绑定。如果当前 widget 发生这种 event ,则调用一次该函数,并将 event 对象输入给函数。- 例:
>>> text1 = tkinter.Text(root) >>> text1.pack() >>> text1.bind('<Key>', lambda event: print(event)) <KeyPress event state=Mod1 keysym=a keycode=65 char='a' x=201 y=126>
- keysym、keycode 表示按下的键名、键值。
- x、y 表示此时鼠标的坐标。
- 每个 widget 可以绑定多种 event 。但同一 widget 的每种 event ,最多绑定一个函数。
>>> text1.bind() # 调用 bind() 时,不传入参数,则会返回该 widget 绑定的所有事件 ('<Key>',) text1.unbind('<Key>') # 取消绑定一种事件 >>> text1.bind() ()
- 例:
常见的几种 event :
<ButtonPress-1> # 鼠标左键按下 <ButtonRelease-1> # 鼠标左键释放 <Double-Button-1> # 鼠标左键双击 # 将上面三个 event 名称中的数字 1 ,改成 2、3,就是指鼠标滚轮、鼠标右键 <Enter> # 鼠标移入当前 widget <Leave> # 鼠标离开当前 widget <Motion> # 鼠标在当前 widget 上方移动 <B1-Motion> # 鼠标在当前 widget 上方,并且在按住鼠标左键的情况下,移动鼠标 <FocusIn> # 当前 widget 得到焦点,比如用户点击了它、按 Tab 切换光标到它 <FocusOut> # 当前 widget 失去焦点 <Key> # 用户按了键盘的任一键 # 这里是指 "按住" ,如果用户一直按住,则会反复触发该 event # 如果用户按键盘时,被输入法挡住,则 event 对象可能记录不到 keysym <Key-Return> # 按住回车键 <Key-y> # 输入小写的 y <Key-Y> # 输入大写的 Y <Shift-Y> # 在按住 Shift 的情况下,输入大写的 Y <Configure> # 窗口的大小改变