【tkGo】使用线程和装饰器

523 阅读2分钟

1 背景

在使用tkinter进行GUI编程时,有时会碰到界面未响应的情况,如下:

2 解决办法

原因很有可能是执行的某个动作阻塞了线程,可通过使用threading.Thread解决(本例中是点击了Go菜单下的开始选项导致的界面卡死)

2.1 解决方法1 - 使用线程封装该动作

修改前代码:

class MenuGo(EMenu):

    LABEL_GO = "Go"
    LABEL_START = "开始"
    LABEL_END = "终止"
    
    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master=master, cnf=cnf, **kw)
        self.go = None
        master.add_cascade(label=self.LABEL_GO, menu=self)
        self.add_command(label=self.LABEL_START, command=self.start)
        self.add_command(label=self.LABEL_END, command=self.end)

    def start(self):
        self.go = True
        while self.go:
            self.stdout("biu biu biu!", with_time=" - ")
            time.sleep(1)
    
    def end(self):
        self.go = False
        self.stdout("zi zi zi!", with_time=" - ")

修改后代码:

class MenuGo(EMenu):

    LABEL_GO = "Go"
    LABEL_START = "开始"
    LABEL_END = "终止"
    
    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master=master, cnf=cnf, **kw)
        self.go = None
        master.add_cascade(label=self.LABEL_GO, menu=self)
        self.add_command(label=self.LABEL_START, command=self.start)
        self.add_command(label=self.LABEL_END, command=self.end)

    def start(self):
        def run():
            self.go = True
            while self.go:
                self.stdout("biu biu biu!", with_time=" - ")
                time.sleep(1)
        t = Thread(target=run)
        t.start()
    
    def end(self):
        self.go = False
        self.stdout("zi zi zi!", with_time=" - ")

2.2 解决方法2 - 使用装饰器类

from functools import wraps
from threading import Thread


class ThreadRun(object):
    def __init__(self):
        pass

    def __call__(self, f):
        @wraps(f)
        def wrapped_f(*args, **kw):
            t = Thread(
                target=f,
                args=args,
                kwargs=kw
            )
            t.start()

        return wrapped_f
class MenuGo(EMenu):

    LABEL_GO = "Go"
    LABEL_START = "开始"
    LABEL_END = "终止"
    
    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master=master, cnf=cnf, **kw)
        self.go = None
        master.add_cascade(label=self.LABEL_GO, menu=self)
        self.add_command(label=self.LABEL_START, command=self.start)
        self.add_command(label=self.LABEL_END, command=self.end)

    @ThreadRun()
    def start(self):
        self.go = True
        while self.go:
            self.stdout("biu biu biu!", with_time=" - ")
            time.sleep(1)
    
    def end(self):
        self.go = False
        self.stdout("zi zi zi!", with_time=" - ")

2.3 解决方法3 - 继承类,添加装饰器函数

from tkinter import Menu
from tkinter import DISABLED, NORMAL
from threading import Thread
from functools import wraps


class EMenu(Menu):
    def __init__(self, *args, **kw):
        self.stdout = kw["stdout"] if "stdout" in kw else print
        self.stderr = kw["stderr"] if "stderr" in kw else print
        for key in ["stdout", "stderr"]:
            if key in kw:
                del kw[key]
        super().__init__(*args, **kw)

    def thread_run(label_name):
        def run_decorator(f):
            @wraps(f)
            def wrapped_f(self, *args, **kw):
                def call():
                    self.entryconfig(label_name, state=DISABLED)  # 设置菜单选项禁用
                    f(self, *args, **kw)
                    self.entryconfig(label_name, state=NORMAL)  # 设置菜单选项启用
                t = Thread(target=call)
                t.start()
            return wrapped_f
        return run_decorator
class MenuGo(EMenu):

    LABEL_GO = "Go"
    LABEL_START = "开始"
    LABEL_END = "终止"
    
    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master=master, cnf=cnf, **kw)
        self.go = None
        master.add_cascade(label=self.LABEL_GO, menu=self)
        self.add_command(label=self.LABEL_START, command=self.start)
        self.add_command(label=self.LABEL_END, command=self.end)

    @EMenu.thread_run(LABEL_START)
    def start(self):
        self.go = True
        while self.go:
            self.stdout("biu biu biu!", with_time=" - ")
            time.sleep(1)
    
    def end(self):
        self.go = False
        self.stdout("zi zi zi!", with_time=" - ")

3 演示效果

4 完整代码

github.com/TheUncleWho…