Python tkinter 實現 指令碼工具 GUI模版

笨豬大難臨頭發表於2020-12-17

簡介

  • 常常需要寫一些指令碼,配置一些引數後就開始執行計算, 並且希望能夠讓他人無障礙使用,會以視覺化方式執行
  • 主要使用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()

打賞

如果覺得文章有用,你可鼓勵下作者

在這裡插入圖片描述

相關文章