Python 之 tkinter 学习笔记
前言
最近有个小需求需要实现,最后要给出一个 GUI 界面,想了想还是不用 c++ 写 MFC 了,因为还涉及到网络编程,感觉还是简单事情简单做,然后转手写 python,刚开始想尝试 pyqt,但感觉好像还是有点麻烦,本来就只是做个插件,最后就大概看了一下内置的 tkinter,一边写一边学也马上就上手了,总的来说感觉还是简单的。
tkinter 简介
Tkinter 是 Tk GUI 工具包的 Python 绑定包。它是 Tk GUI 工具包的标准 Python 接口,并且是 Python 的业界标准 GUI 工具包。
创建一个窗口
由于 python
内置了 tkinter
因此我们不需要安装额外的库,直接导入即可
下面是一个简单的示例,它创建了一个窗口,设置窗口标题,并设置窗口大小和位置
1 2 3 4 5 6 7 8 9 10 11 12 13 import tkinter as tkwindow = tk.Tk() window.title('my_window' ) window.geometry('500x300+500+300' ) window.mainloop()
代码很简单,也不难理解,效果如下:
很多时候,为了美观,我们需要窗口显示在屏幕中样,这时候我们可以通过 winfo_screenwidth()
和 winfo_screenheight()
获取显示区域的宽度和高度,然后将窗口显示在屏幕中央。
1 2 3 4 5 6 7 8 9 10 11 screenWidth = window.winfo_screenwidth() screenHeight = window.winfo_screenheight() width = 500 height = 300 left = (screenWidth - width) / 2 top = (screenHeight - height) / 2 window.geometry("%dx%d+%d+%d" % (width, height, left, top))
添加窗口部件
窗口部件简介
tkinter 同样有许多小部件,例如按钮,文本框,输入框等,将这些组件拼接,就可以得到一个比较完整的桌面程序。
tkinter 类
元素
说明
Button
按钮
在程序中显示按钮
Canvas
画布
提供绘制功能
Checkbutton
多选框
在程序中显示多选框
Combobox
下拉框
显示下拉框
Entry
输入框
显示单行文本内容
Frame
框架
用于放置其他窗口部件
Label
标签
显示文本或位图
Listbox
列表框
显示选择列表
Menu
菜单
显示菜单栏
Message
消息框
类似与标签,可以显示多行文本
Radiobutton
单选按钮
显示单选按钮
Scale
进度条
线性滑块组件
Scrollbar
滚动条
显示一个滚动条
Text
文本框
显示多行文本
messagebox
消息框
弹出一个消息框
设置组件位置
说完了部件之后,我们同样还要考虑放置部件的位置。
tkinter 有三种布局管理方式:
pack()
pack()
是最常用的布局,不需要指定具体位置,当然也可以通过指定位置,边距来实现复杂的布局。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import tkinter as tkwindow = tk.Tk() window.title('my_window' ) window.geometry('500x300' ) label_1 = tk.Label(window, text='label_1' , bg="green" ).pack() label_2 = tk.Label(window, text='label_2' , bg="red" ).pack(fill=tk.X) label_3 = tk.Label(window, text='label_3' , bg="yellow" ).pack(fill=tk.X, padx=100 , pady=50 ) window.mainloop()
效果如下:
grid()
Grid 在很多场景下是最好用的布局方式,它把控件位置作为一个二维表结构来维护,使用一个行列结构来定位每一个元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import tkinter as tkwindow = tk.Tk() window.title('my_window' ) window.geometry('500x300' ) for i in range (9 ): if i % 2 == 0 : bg_color = "white" else : bg_color = "gray" label = tk.Label(window, text=str (i + 1 ), bg=bg_color, width=6 , height=3 ) label.grid(row=i // 3 , column=i % 3 ) window.mainloop()
效果如下:
place()
place()
通过指定控件的绝对位置(或于父控件的相对位置)来布局,非常容易理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import tkinter as tkwindow = tk.Tk() window.title('my_window' ) window.geometry('500x300' ) label_1 = tk.Label(window, text='label_1' , bg='red' ).place(x=20 , y=20 ) label_2 = tk.Label(window, text='label_2' , bg='yellow' ).place(x=100 , y=20 ) label_3 = tk.Label(window, text='label_3' , bg='gray' ).place(x=50 , y=80 ) window.mainloop()
效果如下:
一个简单的示例
下面这段代码添加了几个控件,通过简单的布局,展示了一个常见的登录窗口。
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 import tkinter as tkfrom tkinter import messageboxwindow = tk.Tk() window.title('my_window' ) window.geometry('500x300' ) label_account = tk.Label(window, text='账号:' ).place(x=50 , y=50 ) label_password = tk.Label(window, text='密码:' ).place(x=50 , y=100 ) entry_account = tk.Entry(window) entry_account.place(x=100 , y=50 ) entry_password = tk.Entry(window, show='*' ) entry_password.place(x=100 , y=100 ) btn_login = tk.Button(window, text="登录" ) btn_login.place(x=150 , y=150 ) window.mainloop()
效果如下:
设置控件响应函数
在上面的例子中,我们成功地向窗口中添加了Label
, Entry
, Button
等组件,但此时我们的控件并没有关联任何函数。当你点击按钮时,得不到任何响应,熟悉 GUI 编程的都知道控件都需要一个响应函数,让我们在点击按钮时得到反馈。
具体实现起来也很简单,我们只需要额外定义一个函数,将控件与这个函数绑定即可。
1 2 3 4 5 6 7 8 from tkinter import messageboxdef onClickLogin (): messagebox.showinfo(title='提示' , message='Login' ) btn_login = tk.Button(window, text="登录" , command=onClickLogin) btn_login.place(x=150 , y=150 )
在上面这段代码中,我们定义了一个函数 onClickLogin
,它的功能是弹出一个消息提示框,标题为 提示
,内容为 Login
;同时,对 btn_login
进行了修改,在初始化时添加了 command=onClickLogin
字段,它的功能也就是将按钮 btn_login
与函数 onClickLogin
绑定。
获取并显示账号密码
学会了添加控件响应函数,那么就让我们在之前例子的基础上添加一个小功能:当你输入账号密码之后,点击登录,弹出你输入的账号密码。毕竟在上面的例子中,我们并没有关注输入了什么内容,也没有对账号密码进行保存。
首先,我们需要知道的是有些控件可以通过传入特定参数直接和一个控件绑定,这种绑定是双向的: 如果该变量发生改变, 与该变量绑定的控件也会随之更新
下面的这段代码中,我们就创建了两个 StringVar
类型的变量,并将 account
和 password
分别与 entry_account
,entry_password
进行绑定:
1 2 3 4 5 6 7 account = tk.StringVar() password = tk.StringVar() entry_account = tk.Entry(window, textvariable=account) entry_password = tk.Entry(window, textvariable=password, show='*' )
StringVar
是 tkinter
中变量类的一个,它保存一个 string
类型变量,默认值为 ""
。
当然,类似的也有 IntVar
,DoubleVar
,BooleanVar
,我想你也同样能够理解它的意思。
要得到其保存的变量值, 使用它的 get() 方法即可。
要设置其保存的变量值, 使用它的 set() 方法即可。
1 2 3 4 5 6 7 account = tk.StringVar() account.set ('123456' ) str_account = account.get()
同时,我们还需要对 onClickLogin
进行修改,通过 get()
转换为 string
类型,并通过消息框弹出信息。
1 2 3 def onClickLogin (): msg = '账号:' + account.get() + ' 密码:' + password.get() messagebox.showinfo(title='提示' , message=msg)
完整的代码如下:
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 tkfrom tkinter import messageboxdef onClickLogin (): msg = '账号:' + account.get() + ' 密码:' + password.get() messagebox.showinfo(title='提示' , message=msg) window = tk.Tk() window.title('my_window' ) window.geometry('500x300' ) label_account = tk.Label(window, text='账号:' ).place(x=50 , y=50 ) label_password = tk.Label(window, text='密码:' ).place(x=50 , y=100 ) account = tk.StringVar() password = tk.StringVar() entry_account = tk.Entry(window, textvariable=account) entry_account.place(x=100 , y=50 ) entry_password = tk.Entry(window, textvariable=password, show='*' ) entry_password.place(x=100 , y=100 ) btn_login = tk.Button(window, text="登录" , command=onClickLogin) btn_login.place(x=150 , y=150 ) window.mainloop()
效果如下:
添加更多自定义设置
在上面的示例中,我们并没有过多的关注控件的大小、颜色、字体等信息,但实际上对于大多数的控件,你都可以自定义这些属性。
1 2 3 4 var = tk.StringVar() var.set ('This is a label' ) l = tk.Label(window, textvariable=var, bg='green' , fg='white' , font=('Arial' , 12 ), width=30 , height=2 ) l.pack()
效果如下:
单选、复选、下拉框
对于单选、复选、下拉框,我想大家都不陌生,在我们填写各种表单、问卷的时候就经常见到,这里我们仍然通过一个简单的示例来展示用法。
添加单选框
单选框要求我们从 n
个选项中选择一个选项,因此我们需要将这 n
的单选框都绑定到一个变量上,正如下面代码中展示的,value
属性用于多个单选框值的区别,我们把 rad_gender_1
和 rad_gender_2
都绑定到了变量 gender
,当我们选中了其中一个选项,就会把 value
的值 1 放到变量 gender
中
1 2 3 4 5 6 7 8 gender = tk.IntVar() rad_gender_1 = tk.Radiobutton(window, text='男' , variable=gender, value=1 ) rad_gender_2 = tk.Radiobutton(window, text='女' , variable=gender, value=2 ) rad_gender_1.place(x=100 , y=50 ) rad_gender_2.place(x=150 , y=50 )
添加下拉框
下拉框可以让我们从多个选项中选择一个选项。在下面的示例中,下拉框 combo_birth_year
会将选择的值传递给绑定的变量 birth_year
;另一方面,可以通过设置 value
字段设置待选项。
1 2 3 4 5 6 7 8 9 from tkinter import ttkbirth_year = tk.StringVar() combo_birth_year = ttk.Combobox(window, width=8 , textvariable=label_birth_year) combo_birth_year['value' ] = [str (i) for i in range (1950 , 2021 )] combo_birth_year.place(x=100 , y=80 )
添加多选框
多选框允许我们从 n
个选项中选择 1 - n
个选项。在下面的示例中,我们创建了一个字典存储不同的爱好,同样创建了 n
个多选框实例,并且将值依次存入 dic_hobby
。
1 2 3 4 5 6 7 8 hobbys = {0 : '唱歌' , 1 : '跳舞' , 2 : '篮球' , 3 : '足球' , 4 : '绘画' } dic_hobby = {} for i in range (len (hobbys)): dic_hobby[i] = tk.BooleanVar() cbtn_hobby = tk.Checkbutton(window, text=hobbys[i], variable=dic_hobby[i]) cbtn_hobby.place(x=100 + i * 60 , y=110 )
完整示例
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 import tkinter as tkfrom tkinter import ttkfrom tkinter import messageboxwindow = tk.Tk() window.title('my_window' ) window.geometry('500x300' ) tk.Label(window, text='姓名: ' ).place(x=20 , y=20 ) entry_name = tk.Entry(window).place(x=100 , y=20 ) tk.Label(window, text='性别: ' ).place(x=20 , y=50 ) gender = tk.IntVar() rad_gender_1 = tk.Radiobutton(window, text='男' , variable=gender, value=1 ) rad_gender_2 = tk.Radiobutton(window, text='女' , variable=gender, value=2 ) rad_gender_1.place(x=100 , y=50 ) rad_gender_2.place(x=150 , y=50 ) tk.Label(window, text='出生年月: ' ).place(x=20 , y=80 ) tk.Label(window, text='年 ' ).place(x=180 , y=80 ) tk.Label(window, text='月 ' ).place(x=290 , y=80 ) birth_year = tk.StringVar() combo_birth_year = ttk.Combobox(window, width=8 , textvariable=birth_year) combo_birth_year['value' ] = [str (i) for i in range (1950 , 2021 )] combo_birth_year.place(x=100 , y=80 ) birth_mon = tk.StringVar() combo_birth_mon = ttk.Combobox(window, width=8 , textvariable=birth_mon) combo_birth_mon['value' ] = [str (i) for i in range (1 , 13 )] combo_birth_mon.place(x=210 , y=80 ) tk.Label(window, text='爱好: ' ).place(x=20 , y=110 ) hobbys = {0 : '唱歌' , 1 : '跳舞' , 2 : '篮球' , 3 : '足球' , 4 : '绘画' } dic_hobby = {} for i in range (len (hobbys)): dic_hobby[i] = tk.BooleanVar() cbtn_hobby = tk.Checkbutton(window, text=hobbys[i], variable=dic_hobby[i]) cbtn_hobby.place(x=100 + i * 60 , y=110 ) window.mainloop()
效果如下:
Canvas 画布
Canvas,提供绘图功能,提供的图形组件包括:线形, 圆形, 图片…
类似的,我们使用如下命令创建一个 Canvas 实例,为了明显,我们将背景色设定为黄色
1 cv = tk.Canvas(window, bg='yellow' )
下面的例子中,我们绘制了一条直线,从 (0, 50)
到 (80, 80)
;绘制了一个矩形,它的左上和右下顶点的坐标分别是 (30, 100)
, (70, 150)
;最后通过 create_image()
导入了一张图片。
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 import tkinter as tkwindow = tk.Tk() window.title('my_window' ) window.geometry('500x300' ) cv = tk.Canvas(window, bg='yellow' ) cv.pack() line = cv.create_line(0 , 50 , 80 , 80 ) rect = cv.create_rectangle(30 , 100 , 70 , 150 ) img = tk.PhotoImage(file='bubblesort.gif' ) cv.create_image(100 , 100 , anchor='nw' , image=img) window.mainloop()
效果如下:
菜单栏和子窗口
添加菜单栏
菜单功能同样是比较常见的,我们可以在各种软件上发现菜单。在 tkinter
中,同样可以很容易地添加菜单栏。
在下面的代码中,我们首先创建了一个菜单栏 menubar
,接着又创建了两个菜单项 menu_file
和 menu_edit
,并通过 add_cascade()
将两个菜单项 File
和 Edit
添加到菜单栏中;然后又在菜单项 File
中加入内容 new
,open
,save
等字段,这里没有实现具体的功能,你可以自己添加 command
参数以实现响应。最后,还需要设置主窗口的 menu
参数,将 menubar
配置到窗口中。
类似的,你也可以通过设定层次关系实现二级、三级菜单,只需要正确的指定父子 menu
即可。
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 import tkinter as tkwindow = tk.Tk() window.title('my_window' ) window.geometry('500x300' ) menubar = tk.Menu(window) menu_file = tk.Menu(menubar, tearoff=0 ) menu_edit = tk.Menu(menubar, tearoff=0 ) menubar.add_cascade(label='File' , menu=menu_file) menubar.add_cascade(label='Edit' , menu=menu_edit) menu_file.add_cascade(label='new' ) menu_file.add_cascade(label='open' ) menu_file.add_separator() menu_file.add_cascade(label='save' ) window.config(menu=menubar) window.mainloop()
效果如下:
添加子窗口
很多情况下,一个窗口往往不足以展示我们需要的全部信息,因此这时候我们可以创建子窗口
下面的例子中,我们在前面的基础上为 File
菜单项中的 new
按钮添加了事件函数 onClickNew()
,它会创建一个子窗口 sub_window
,注意此时创建出来的窗口必须是 Toplevel
。
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 import tkinter as tkdef onClickNew (): sub_window = tk.Toplevel(window) sub_window.title('sub_window' ) sub_window.geometry('300x200' ) tk.Label(sub_window, text='This is a sub window' ).pack() window = tk.Tk() window.title('my_window' ) window.geometry('500x300' ) menubar = tk.Menu(window) menu_file = tk.Menu(menubar, tearoff=0 ) menu_edit = tk.Menu(menubar, tearoff=0 ) menubar.add_cascade(label='File' , menu=menu_file) menubar.add_cascade(label='Edit' , menu=menu_edit) menu_file.add_cascade(label='new' , command=onClickNew) menu_file.add_cascade(label='open' ) menu_file.add_separator() menu_file.add_cascade(label='save' ) window.config(menu=menubar) window.mainloop()
效果如下:
文件对话框
下面让我们来实现一个小功能,点击 选择路径
按钮,打开文件对话框,选定路径后列出该路径下的所有文件和文件夹。
让我们一步一步来实现,首先,我们需要做出一个界面,大概想想你见过的文件选择对话框,我相信这并不困难。
1 2 3 4 5 6 7 8 9 10 window_list = tk.Text(window, width=60 , height=10 , state='disabled' ) window_list.place(x=30 , y=70 ) path = tk.StringVar() tk.Label(window, text="目标路径:" ).place(x=50 , y=30 ) tk.Entry(window, textvariable=path, width=30 ).place(x=120 , y=30 ) tk.Button(window, text="路径选择" , command=onClickSelectPath).place(x=350 , y=25 )
上面的代码我相信已经很熟悉了,我们设计了布局,在 路径选择
按钮上添加了函数 onClickSelectPath()
。值得注意的是,我们将 Text
设为禁止,这意味你不能写入任何字段。
现在让我们来看看 onClickSelectPath()
怎么实现,我们可以通过添加 askdirectory()
函数请求目录;然后通过 set()
更新 path
的路径,注意这里 path
是和 Entry
绑定了,因此更新了 path
之后,Entry
中会自动显示该路径。
1 2 3 4 5 from tkinter.filedialog import askdirectorydef onClickSelectPath (): _path = askdirectory() path.set (_path)
得到了文件路径之后,我们便可以通过 listdir()
获得所有文件。
1 2 file_lists = os.listdir(file_dir)
接着,我们只需要把获得的文件写入 Text
,由于之间我们在创建时将 Text
设为了禁止,因此在写入数据之间,需要将其重置为 normal
,等到写入完成之后再 disabled
。
我们使用 delete
和 insert
进行数据的删除和插入,你只需要指定插入的位置
和内容
即可。
1 2 3 4 5 6 7 8 9 10 11 window_list.config(state='normal' ) window_list.delete(1.0 , tk.END) for file_name in file_lists: window_list.insert(tk.END, file_name) window_list.insert(tk.END, '\n' ) window_list.config(state='disabled' )
完整代码如下所示:
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 import osimport tkinter as tkfrom tkinter.filedialog import askdirectorydef onClickSelectPath (): _path = askdirectory() path.set (_path) file_dir = path.get() if file_dir: file_lists = os.listdir(file_dir) window_list.config(state='normal' ) window_list.delete(1.0 , tk.END) for file_name in file_lists: window_list.insert(tk.END, file_name) window_list.insert(tk.END, '\n' ) window_list.config(state='disabled' ) window = tk.Tk() window.title('my_window' ) window.geometry('500x300' ) window_list = tk.Text(window, width=60 , height=10 , state='disabled' ) window_list.place(x=30 , y=70 ) path = tk.StringVar() tk.Label(window, text="目标路径:" ).place(x=50 , y=30 ) tk.Entry(window, textvariable=path, width=30 ).place(x=120 , y=30 ) tk.Button(window, text="路径选择" , command=onClickSelectPath).place(x=350 , y=25 ) window.mainloop()
效果如下:
打包为 exe
写完了程序之后,我们不可能直接丢给别人一个 py
文件,还要将其打包为 exe
。
目前比较常见的打包 exe
方法都是通过 pyinstaller
来实现的,使用安装命令进行安装:
pyinstaller 打包 exe
进入命令行界面,进入当前 .py
所在的目录,也就是你要打包的文件,(当然简单的方式是按住 shift
然后右键,进入命令行界面)
然后输入如下命令:
另外你也可以指定 pyinstaller
的参数:
1 2 3 4 5 6 pyinstaller -F py_word.py pyinstaller -F -w py_word.py pyinstaller -F -w -i chengzi.ico py_word.py
参考资料