简介
- 常常需要写一些脚本,配置一些参数后就开始执行计算, 并且希望能够让他人无障碍使用,会以可视化方式执行
- 主要使用tkinter库, 布局方面, 每一行构造一个Frame然后pack到窗口即可.而每一行的Frame可采用pack或者grid或者place布局即可. 以此类推可以方面扩展多个控件
简单工作流程
- 窗口每个按钮控件会产生事件, 然后一般会回调函数, 但是它是在主线程里回调的是阻塞的,不可能我们每次就创建线程去异步执行, 所以用一个消息队列来解耦按钮事件和回调处理函数, 本来队列的消息被消费者消费后直接判断消息类型消费即可,但是为了代码简洁交给另一个类处理.
效果
源码
- 在start_demo函数执行自定义计算即可
import queue
import threading
import time
import traceback
from tkinter import Tk, Button, messagebox, Label, Frame, LEFT, Entry, IntVar, Radiobutton, StringVar, Checkbutton, \
BooleanVar, RIGHT, constants, Scale, HORIZONTAL, Variable, filedialog, Text, font, Scrollbar
from tkinter.font import Font, BOLD
from tkinter.messagebox import showinfo, showerror, showwarning
from tkinter.ttk import Combobox, Widget
from enum import Enum
from import GuiTempldate
class MsgEnum(Enum):
"""
消息类型枚举
"""
START = 0
STOP = 1
EXIT = 3
def start_demo(gui: GuiTempldate):
""" 自定义计算任务
:param gui: gui组件对象
:return:
"""
count = 20
while gui.Cache.RUNING:
count -= 1
gui.text_btn.insert(constants.END, f"cd {count}\n")
gui.text_btn.see(constants.END)
time.sleep(1)
class GuiTempldate:
class Cache:
RUNING = False
def __init__(self) -> None:
# 1、Gui消息队列
self.msg_center = MsgCenter(self)
# 2、窗口设置
self.root = Tk()
self.root.title("xxx工具")
# self.root.wm_resizable(False, False) # 设置窗口大小不可拉伸
self.root.geometry('500x600+500+200') # 设置窗口: 宽 x 高 + 窗口位置x坐标 + 窗口位置y坐标
self.root.protocol("WM_DELETE_WINDOW", self.close_event)
# 3、定义组件, -- 带btn后缀是控件Widget对象, 带var后缀是控件绑定的值Variable对象
self.url_var = None
self.url_btn = None
self.mode_var = None
self.name_var = None
self.name_btn = None
self.is_xx_btn = None
self.is_xx_var = None
self.level_var = None
self.scale_btn = None
self.start_btn = None
self.stop_btn = None
self.select_file_var = None
self.text_btn = None
# 4、初始化各个组件和布局
self.initGui()
def initGui(self):
# 1- 标签
text_str = """版本: 2.0.1
#author burukeyou
#说明:
1) 这是关于
2) 请先开始再停止"""
Label(self.root, text=text_str, justify='left', fg='red').pack(anchor=constants.W)
# 2- 长文本框
Label(self.root, text='请求接口').pack(anchor=constants.W)
self.url_var = StringVar(value="http://www.baidu.com")
self.url_btn = Entry(self.root, width=60, textvariable=self.url_var)
self.url_btn.pack(anchor=constants.W)
# 3- 单选框
self.mode_var = IntVar(value=1)
fm01 = Frame(self.root) # , bg='blue'
fm01.pack(anchor=constants.W)
Label(fm01, text='模式').grid(row=0, column=0, sticky='W')
Radiobutton(fm01, text='a模式', variable=self.mode_var, value=1).grid(row=0, column=1, sticky='E', padx=40)
Radiobutton(fm01, text='b模式', variable=self.mode_var, value=2).grid(row=0, column=2, sticky='E', padx=40)
Radiobutton(fm01, text='c模式', variable=self.mode_var, value=3).grid(row=0, column=3, sticky='E', padx=40)
# 4- 文本框
fm02 = Frame(self.root)
self.name_var = StringVar(value=100)
fm02.pack(anchor=constants.W, fill=constants.X)
Label(fm02, text='名字 ').pack(side=constants.LEFT)
self.name_btn = Entry(fm02, width=20, textvariable=self.name_var)
self.name_btn.pack(side=constants.RIGHT)
# 5- 勾选框
self.is_xx_var = BooleanVar()
self.is_xx_btn = Checkbutton(self.root, variable=self.is_xx_var, text='是否xxx', onvalue=True, offvalue=False)
self.is_xx_btn.pack(anchor=constants.W)
# 6-下拉选择
fm03 = Frame(self.root)
fm03.pack(anchor=constants.W, fill=constants.X)
self.level_var = StringVar()
Label(fm03, text='统计级别 ').pack(side=constants.LEFT)
com = Combobox(fm03, textvariable=self.level_var)
com.pack(side=constants.RIGHT)
com["value"] = ("级别0", "级别1", "级别2")
com.current(1)
# 7- 范围选择组件
fm04 = Frame(self.root)
fm04.pack(anchor=constants.W, fill=constants.X)
Label(fm04, text='范围: ').pack(side=constants.LEFT)
self.scale_btn = Scale(fm04, from_=0, to=100, orient=constants.HORIZONTAL, tickinterval=100, length=200)
self.scale_btn.pack(side=constants.RIGHT)
self.scale_btn.set(20)
# 8- 文件选择组件
fm05 = Frame(self.root)
fm05.pack(anchor=constants.W, fill=constants.X)
self.select_file_var = StringVar(value="你没有选择任何文件")
Button(fm05, text="选择处理的文件", command=self.click_file_event).pack(side=constants.LEFT)
Label(fm05, textvariable=self.select_file_var).pack(side=constants.RIGHT)
# 9- 多行文本框
fm22 = Frame(self.root)
fm22.pack(anchor=constants.W, fill=constants.X)
scroll = Scrollbar(fm22)
scroll.pack(side=constants.RIGHT, fill=constants.Y)
ft = Font(family='微软雅黑', size=18, weight=font.BOLD)
self.text_btn = Text(fm22, height=9, fg="green", font=ft, bg="black", insertbackground="red")
self.text_btn.pack(side=constants.LEFT)
# text 联动 scroll
scroll.config(command=self.text_btn.yview)
self.text_btn.config(yscrollcommand=scroll.set)
# 10、开始结束按钮
fm06 = Frame(self.root)
self.start_btn = Button(fm06, text="开始", width=6, height=1, command=self.start_event)
self.start_btn.grid(row=0, column=0, sticky='W', padx=20, pady=20)
self.stop_btn = Button(fm06, text="停止", width=6, height=1, command=self.stop_event)
self.stop_btn.grid(row=0, column=1, sticky='E', padx=20, pady=20)
fm06.pack(side=constants.BOTTOM)
def start_event(self):
self.msg_center.put(MsgEnum.START)
self.Cache.RUNING = True
# todo 开始脚本执行、把当前Gui对象传过去就可以获得各个控件的值
threading.Thread(target=start_demo, args=(self,)).start()
def stop_event(self):
self.msg_center.put(MsgEnum.STOP)
self.Cache.RUNING = False
def click_file_event(self):
filename = filedialog.askopenfilename()
if filename != '':
# 设置标签文本
self.select_file_var.set(filename)
print(self.select_file_var.get())
def close_event(self):
if self.Cache.RUNING and not messagebox.askokcancel("警告", "任务还在执行中,确定要关闭吗?"):
return
self.root.destroy()
self.msg_center.put(MsgEnum.EXIT)
def showUI(self):
threading.Thread(target=self.msg_center.mainloop).start()
# 这个方法会循环阻塞住,监听gui的事件,得放到主线程最后
self.root.mainloop()
class MsgCenter:
"""
消息队列
主要处理窗口控件消息
"""
def __init__(self, obj: GuiTempldate) -> None:
self.queue = queue.Queue()
self.obj = obj
def put(self, msg: Enum):
self.queue.put(msg)
# 循环消费消息
def mainloop(self):
while True:
try:
# 阻塞获取消息
msg = self.queue.get()
print("消费消息: {}".format(msg))
if msg == MsgEnum.START:
MsgStrategy.start_strategy(self.obj)
elif msg == MsgEnum.STOP:
MsgStrategy.stop_strategy(self.obj)
elif msg == MsgEnum.EXIT:
break
else:
pass
except queue.Empty:
traceback.print_exc()
class MsgStrategy:
@classmethod
def start_strategy(cls, gui: GuiTempldate):
gui.start_btn.config(state=constants.DISABLED)
gui.stop_btn.config(state=constants.NORMAL)
gui.is_xx_btn.config(state=constants.DISABLED)
gui.scale_btn.config(state=constants.DISABLED)
gui.url_btn.config(state=constants.DISABLED)
gui.name_btn.config(state=constants.DISABLED)
# 获得各个组件的值
val = f"""
接口:[{gui.url_var.get()}]
模式:[{gui.mode_var.get()}]
名字:[{gui.name_var.get()}]
是否xx: [{gui.is_xx_var.get()}]
统计级别:[{gui.level_var.get()}]
范围: [{gui.scale_btn.get()}]
文件: [{gui.select_file_var.get()}]
"""
showinfo("", val)
@classmethod
def stop_strategy(cls, gui: GuiTempldate):
gui.start_btn.config(state=constants.NORMAL)
gui.stop_btn.config(state=constants.DISABLED)
gui.is_xx_btn.config(state=constants.NORMAL)
gui.scale_btn.config(state=constants.NORMAL)
gui.url_btn.config(state=constants.NORMAL)
gui.name_btn.config(state=constants.NORMAL)
if __name__ == '__main__':
gui = GuiTempldate()
gui.showUI()