最近更新于 2024-05-05 12:32
前言
这些主要是 21 年的时候学习 Python 的官方图形库 Tkinter 时写的,在写比较简单的工具的时候用这个库会比较方便,也不依赖第三方,这些 demo 算是 tk 的简单应用,对于有需要的可以用作参考。
环境
编写时的测试环境:Python 3.8.11
本文再次验证的环境:
Ubuntu 22.04(Windows 11 专业工作站版 22H2 WSL2)
Python 3.11.3
源码注明的除外
代码
框架(Pack 布局)
"""
@brief Tkinter 面向对象设计框架
Copyright (C) 2021 IYATT-yx iyatt@iyatt.com
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from tkinter import *
import tkinter.messagebox as messagebox
import tkinter.font as font
class Application(Frame):
def __init__(self, master=None):
super().__init__(master) # 父类
self.__master = master
self.pack()
self.createWidget()
def createWidget(self):
# 标签组件
self.label = Label(
self, text='这是一个标签', width=10, height=1, bg='red', fg='black'
)
self.label.pack()
# 按钮组件
self.btn1 = Button(self)
self.btn1['text'] = '按钮'
self.btn1['command'] = self.doWork
self.btn1['anchor'] = NE # 文字在按钮中的位置 north east
self.btn1.config(width=5, height=5)
self.btn1.pack()
# 单行输入框
self.entryVar = StringVar(value='hello')
self.entry = Entry(self, textvariable=self.entryVar)
self.entry.pack()
self.btn2 = Button(self, text='查看单行框', command=self.viewEntry)
self.btn2.pack()
# 多行输入框
self.text = Text(self, width=40, height=5, bg='blue')
self.text.pack()
self.btn3 = Button(self, text='查看多行框', command=self.viewText)
self.btn3.pack()
# 单选框
self.radioVar = StringVar()
self.radioVar.set('单选一') # 默认初始选中
self.radio1 = Radiobutton(self, text='单选一', value='单选一', variable=self.radioVar)
self.radio2 = Radiobutton(self, text='单选二', value='单选二', variable=self.radioVar)
self.radio1.pack(side='left')
self.radio2.pack(side='left')
# 多选框
self.checkVar1 = IntVar()
self.checkVar2 = IntVar()
self.check1 = Checkbutton(
self, text='选项一', variable=self.checkVar1, onvalue=1, offvalue=0
)
self.check2 = Checkbutton(
self, text='选项二', variable=self.checkVar2, onvalue=1, offvalue=0
)
self.check1.pack(side='right')
self.check2.pack(side='right')
# 画布
self.canvas = Canvas(self, width=100, height=100, bg='green')
self.canvas.pack()
self.canvas.create_line(10, 10, 30, 30, 50, 50, 70, 10, 90, 90)
self.canvas.create_rectangle(20, 20, 80, 90)
# 退出按钮
self.btnQuit = Button(self, text='退出', command=self.__master.destroy)
self.btnQuit.pack()
def doWork(self):
messagebox.showinfo('提示', '按钮被点击')
# 查看你单行输入框的值
def viewEntry(self):
self.text.insert(INSERT, self.entryVar.get())
# messagebox.showinfo('查看单行框', self.entryVar.get())
messagebox.showinfo('查看单行框', self.entry.get())
def viewText(self):
messagebox.showinfo('查看多行框', self.text.get(1.0, END))
if __name__ == '__main__':
root = Tk()
root.geometry('400x500+300+200') # 400x200 左距300 上距200
defaultFont = font.Font(family='黑体', size=12) # 字体
root.option_add("*Font", defaultFont)
root.title('Tkinter 面向对象设计框架示例')
app = Application(master=root)
root.mainloop()
选牌(place 布局)
图像资源下载:resources
文件下载解压后得到的文件夹放到源码所在的目录下
#!/usr/bin/env python3
"""
@brief 选牌
Copyright (C) 2021 IYATT-yx iyatt@iyatt.com
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from tkinter import *
import os
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.__master = master
self.pack()
def createWidgets(self):
abspath = os.path.dirname(os.path.abspath(__file__))
self.photo = [
PhotoImage(file=abspath + '/resources/' + str(i) + '.png') for i in range(9)
]
self.label = [Label(self.__master, image=self.photo[i]) for i in range(9)]
for i in range(0, 9):
self.label[i].place(x=30 + i * 30, y=120)
self.label[0].bind_class('Label', '<Button-1>', self.play)
# 选牌
def play(self, event):
if event.widget.winfo_y() == 120:
event.widget.place(y=90)
else:
event.widget.place(y=120)
if __name__ == '__main__':
root = Tk()
root.geometry('400x400+400+400')
root.title('选牌')
app = Application(root)
app.createWidgets()
root.mainloop()
点击选牌
登录界面+计算器(Grid 布局)
"""
@brief 登录界面+计算器
Copyright (C) 2021 IYATT-yx iyatt@iyatt.com
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from tkinter import *
class GridApplication(Frame):
def __init__(self, master=None):
super().__init__(master)
self.__master = master
self.pack()
def createWidget(self):
Label(self, text='用户名').grid(column=0, row=0)
self.entry1 = Entry(self)
self.entry1.grid(column=1, row=0)
Label(self, text='密码').grid(column=0, row=1)
self.entry2 = Entry(self, show='*')
self.entry2.grid(column=1, row=1)
Button(self, text='登录').grid(column=1, row=2, sticky=EW)
Button(self, text='退出', command=self.__master.destroy).grid(
column=2, row=2, sticky=E
)
# 计算器布局
class Calculator(Frame):
def __init__(self, master=None):
super().__init__(master)
self.__master = master
self.pack()
def createWidget(self):
# 计算式显示框
Text(self, height=3).grid(column=0, row=0, columnspan=4, sticky=EW)
Button(self, text='7').grid(column=0, row=1, sticky=EW)
Button(self, text='8').grid(column=1, row=1, sticky=EW)
Button(self, text='9').grid(column=2, row=1, sticky=EW)
Button(self, text='÷').grid(column=3, row=1, sticky=EW)
Button(self, text='4').grid(column=0, row=2, sticky=EW)
Button(self, text='5').grid(column=1, row=2, sticky=EW)
Button(self, text='6').grid(column=2, row=2, sticky=EW)
Button(self, text='×').grid(column=3, row=2, sticky=EW)
Button(self, text='1').grid(column=0, row=3, sticky=EW)
Button(self, text='2').grid(column=1, row=3, sticky=EW)
Button(self, text='3').grid(column=2, row=3, sticky=EW)
Button(self, text='-').grid(column=3, row=3, sticky=EW)
Button(self, text='0').grid(column=0, row=4, sticky=EW)
Button(self, text='.').grid(column=1, row=4, sticky=EW)
Button(self, text='=').grid(column=2, row=4, sticky=EW)
Button(self, text='+').grid(column=3, row=4, sticky=EW)
if __name__ == '__main__':
grid = Tk()
grid.geometry('300x100+300+400')
grid.title('grid布局演示')
gridApp = GridApplication(grid)
gridApp.createWidget()
calc = Tk()
calc.geometry('700x200+700+400')
calc.title('计算器布局')
calcApp = Calculator(calc)
calcApp.createWidget()
mainloop()
选项菜单
#!/usr/bin/env python3
"""
@brief 选项菜单
Copyright (C) 2021 IYATT-yx iyatt@iyatt.com
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from tkinter import *
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.__master = master
self.pack()
def createWidgets(self):
var = StringVar()
var.set('第一个选项')
self.__om = OptionMenu(self.__master, var, '第一个选项', '第二个选项', '第三个选项')
self.__om['width'] = 50
self.__om.pack()
if __name__ == '__main__':
root = Tk()
root.geometry('300x100+400+400')
root.title('选项菜单')
app = Application(root)
app.createWidgets()
root.mainloop()
滑块
#!/usr/bin/env python3
"""
@brief 滑块
Copyright (C) 2021 IYATT-yx iyatt@iyatt.com
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from tkinter import *
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.__master = master
self.pack()
def showVar(self, value):
print('滑块值:', value)
def createWidgets(self):
Scale(
self.__master,
from_=0,
to=50,
length=500,
tickinterval=5,
orient=HORIZONTAL,
command=self.showVar,
).pack()
if __name__ == '__main__':
root = Tk()
root.geometry('600x100+400+400')
root.title('滑块')
app = Application(root)
app.createWidgets()
root.mainloop()
记事本
"""
@brief 记事本
Copyright (C) 2021 IYATT-yx iyatt@iyatt.com
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from tkinter import *
from tkinter.filedialog import *
from tkinter.messagebox import *
from tkinter.colorchooser import *
import os
class Notepad(Frame):
def __init__(self, master=None):
super().__init__(master)
self.__master = master
self.pack()
self.__saveType = 'newFile'
self.__master.protocol("WM_DELETE_WINDOW", self.__onClosing)
def createWidgets(self):
""" 下拉菜单
"""
# 主菜单栏
self.__menubar = Menu(self.__master)
self.__master.config(menu=self.__menubar)
# 文件
self.__fileMenu = Menu(self.__menubar)
self.__menubar.add_cascade(label='文件', menu=self.__fileMenu)
# 设置
self.__setMenu = Menu(self.__master)
self.__menubar.add_cascade(label='设置', menu=self.__setMenu)
# 帮助
self.__aboutMenu = Menu(self.__master)
self.__menubar.add_cascade(label='帮助', menu=self.__aboutMenu)
# 文件 - 子选项
self.__fileMenu.add_command(label='新建', command=self.__newFile)
self.__fileMenu.add_command(label='打开', command=self.__openFile)
self.__fileMenu.add_command(label='保存', command=self.__saveFile)
self.__fileMenu.add_command(label='另存为', command=self.__saveAs)
# 设置 - 子选项
self.__setMenu.add_command(label='背景颜色', command=self.__setBg)
# 帮助 - 子选项
self.__aboutMenu.add_command(label='关于', command=self.__about)
# 文本框
self.__text = Text()
self.__text.place(relx=0.001, rely=0.001, relwidth=0.998, relheight=0.998)
# 文本框内容修改事件响应
self.__isChanged = False
self.__text.bind('<KeyPress>', self.__changeFlag)
def __newFile(self):
""" 新建文件
"""
self.__saveType = 'newFile'
if self.__isChanged == True:
if askyesnocancel(title='提示', message='是否保存?'):
self.__saveFile()
else:
return
self.__text.delete(1.0, END)
def __openFile(self):
""" 打开文件
"""
self.__saveType = 'openFile'
if self.__isChanged == True:
if self.__isChanged == True:
confirm = askyesnocancel(title='提示', message='是否保存?')
if confirm == True:
self.__saveFile()
elif confirm == None:
return
else:
pass
self.__fileName = askopenfilename(title='打开文件')
if not self.__fileName:
return
with open(self.__fileName, 'r') as f:
self.__text.delete(1.0, END)
self.__text.insert(INSERT, f.read())
self.__isChanged = False
def __saveFile(self):
""" 保存文件
"""
if self.__saveType == 'newFile' or self.__saveType == 'saveAs':
self.__fileName = asksaveasfilename(
title='新建文件',
initialfile='未命名.txt',
filetypes=[('文本文档', '*.txt')],
defaultextension='*.txt',
)
elif self.__saveType == 'openFile':
pass
if not self.__fileName:
return
with open(self.__fileName, 'w') as f:
f.write(self.__text.get(1.0, END))
self.__isChanged = False
def __saveAs(self):
""" 另存为
"""
self.__saveType = 'saveAs'
self.__saveFile()
def __changeFlag(self, key):
""" 文本框修改标志
"""
self.__isChanged = True
def __onClosing(self):
""" 关闭事件回调
"""
if self.__isChanged == True:
confirm = askyesnocancel(title='提醒', message='是否保存?')
if confirm == True:
self.__saveFile()
elif confirm == None:
return
else:
pass
self.__master.destroy()
def __setBg(self):
""" 设置背景颜色
"""
self.__bg = askcolor(color='white', title='选择背景颜色')
self.__text.config(bg=self.__bg[1])
def __about(self):
""" 关于
"""
showinfo('关于', 'Copyright (C) 2021 IYATT-yx\niyatt@iyatt.com')
def main():
root = Tk()
root.geometry('600x400+300+300')
root.title('记事本')
# 设置图标
# absPath = os.path.dirname(os.path.abspath(__file__))
# icon = PhotoImage(file=absPath + '/notepad.png')
# root.iconphoto(False, icon)
# 记事本界面实例化
app = Notepad(root)
app.createWidgets()
root.mainloop()
if __name__ == '__main__':
main()
可以新建、修改以及保存文件,在文本框内容发生修改后,试图新建、打开、关闭等操作会询问是否保存,可以设置背景显示颜色
简易图片查看器
"""
@brief 简易图片查看器
Copyright (C) 2021 IYATT-yx iyatt@iyatt.com
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
# pip install opencv-python==4.7.0.72
import cv2
import tkinter as tk
# pip install Pillow==9.5.0
# 注意:Tkinter 和 PIL 中都有名字为 Image 的类,所以这里导入 Tkinter 没有使用通配符,以避免出现错误
from PIL import ImageTk, Image, UnidentifiedImageError
from tkinter.filedialog import *
from tkinter.messagebox import *
class Viewer(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self._master = master
self.pack()
def createWidgets(self):
global img
self.imgLabel = tk.Label(self)
self.imgLabel.pack()
self._openWay = tk.IntVar(value=1)
self.pillow = tk.Radiobutton(self, text='Pillow', value=1, variable=self._openWay)
self.opencv = tk.Radiobutton(self, text='OpenCV', value=2, variable=self._openWay)
self.pillow.pack()
self.opencv.pack()
tk.Button(self, text='打开', command=self.openImg).pack()
def openImg(self):
global img
fileName = askopenfilename(title='打开文件')
if not fileName:
return
try:
if self._openWay.get() == 1: # PIL 打开图片
img = ImageTk.PhotoImage(image=Image.open(fileName))
elif self._openWay.get() == 2: # OpenCV 打开图片
src = cv2.imread(fileName, cv2.IMREAD_COLOR)
if src is None:
raise UnidentifiedImageError('不能识别图像文件 \'{}\''.format(fileName))
src = cv2.cvtColor(src, cv2.COLOR_BGR2RGB) # OpenCV 图像数据为 BGR 的 NumPy 数组,需要先转为 RGB
img = ImageTk.PhotoImage(image=Image.fromarray(src))
except Exception as e:
showerror("错误", e)
return
self.imgLabel.config(image=img) # 更新图片显示
def main():
global img
img = ''
root = tk.Tk()
root.geometry('600x500+300+200')
root.title('简单图片查看器')
viewer = Viewer(root)
viewer.createWidgets()
root.mainloop()
if __name__ == '__main__':
main()
这里提供了两种思路打开图片文件,一种是直接使用 Pillow,另外一种是使用 OpenCV,然后再转换数据类型(搞懂这个,就可以将 OpenCV 与 Tkinter 融合开发),其它一些图像数据类型可以参考:https://blog.iyatt.com/?p=2592 。
摄像头预览
"""
@brief 摄像头预览
Copyright (C) 2023 IYATT-yx iyatt@iyatt.com
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
# Windows 11 专业工作站版 22H2
# Python 3.11.4
# pip install opencv-python==4.7.0.72
import tkinter as tk
import cv2
# pip install Pillow==9.5.0
from PIL import ImageTk, Image
class CameraPreview(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.__master = master
self.__master.title('摄像头预览')
self.__master.geometry('800x600')
self.__master.protocol('WM_DELETE_WINDOW', self.__on_closing) # 关闭事件
self.pack()
self.__cap = None # 视频捕获对象
self.__state = False # 图像更新标志
self.__create_widgets()
def __create_widgets(self):
""" 创建窗口组件 """
# 图像预览
self.__image_preview_label = tk.Label(self, text='暂无图像')
self.__image_preview_label.pack()
# 视频源输入
self.__video_source_entry = tk.Entry(self)
self.__video_source_entry.pack()
# 视频源打开
tk.Button(self, text='打开', command=self.__on_open_video_source_button).pack()
# 图像预览更新
self.__master.after(100, self.__update_image_preview_label)
def __on_open_video_source_button(self):
""" 打开按钮回调 """
self.video_source = self.__video_source_entry.get()
self.__state = False
if self.__cap != None:
self.__cap.release()
print('释放旧视频资源')
if len(self.video_source) == 0:
self.__cap = None
print('请填写视频源')
return
if self.video_source.isdigit():
self.video_source = int(self.video_source)
try:
self.__cap = cv2.VideoCapture(self.video_source)
except Exception as e:
print(e)
if not self.__cap.isOpened():
self.__cap = None
print('请检查视频源 {} 是否可用'.format(self.video_source))
return
print('打开视频源:{}'.format(self.video_source))
self.__state = True
def __on_closing(self):
""" 关闭事件处理 """
if self.__cap != None:
self.__cap.release()
print('释放视频资源')
self.__master.destroy()
print('销毁窗口')
def __image_convert(self, src):
""" OpenCV 图像转 Tkinter """
src = cv2.cvtColor(src, cv2.COLOR_BGR2RGB)
return ImageTk.PhotoImage(image=Image.fromarray(src))
def __update_image_preview_label(self):
if self.__state:
ret, src = self.__cap.read()
if ret:
self.__img = self.__image_convert(src)
self.__image_preview_label.config(image=self.__img)
self.__master.after(100, self.__update_image_preview_label)
def main():
root = tk.Tk()
cp = CameraPreview(root)
cp.mainloop()
if __name__ == '__main__':
main()
Python Tkinter 学习 demo