Python tkinter implements script tool GUI template

Introduction

  • It is often necessary to write some scripts, configure some parameters and start calculations, and hope to make it accessible to others, and will execute it in a visual manner
  • Mainly use the tkinter library, in terms of layout, you can construct a Frame per line and pack it to the window. Frames in each line can be laid out by pack or grid or place. By analogy, multiple controls can be extended

Simple workflow

  • Each button control of the window will generate events, and then generally call back functions, but it is blocked in the main thread to call back. It is impossible for us to create threads to execute asynchronously every time, so we use a message queue to decouple button events And the callback processing function, the message of the original queue is consumed by the consumer to directly determine the message type consumption, but for the sake of code brevity, it is handed over to another class for processing.

Insert picture description here

effect

Insert picture description here

Source code

  • Perform custom calculations in the start_demo function
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()

Reward

If you find the article useful, you can encourage the author

Insert picture description here

Guess you like

Origin blog.csdn.net/weixin_41347419/article/details/111306949