tkinter 标签在函数调用之前没有得到更新

tkinter Label not getting updated before function call

在下面的代码片段中(在 convert 函数中),在 time_consuming_func 为 运行 之前,状态标签未设置为“正在处理”。而是在函数之后更新。我正在使用标签来指示程序正在执行耗时的功能,因此这违背了拥有状态标签的目的。

我注意到如果我在 self.status.set("Processing") 之后放置一个断点,标签确实会更新。

如何正确更改状态标签,即在 time_consuming_func 之前?

import os
import time
import tkinter as tk
from tkinter import filedialog as fd
from tkinter import ttk
from tkinter.messagebox import showerror
from typing import Any, Tuple

WINDOW_SIZE = "550x300"

FILETYPES = (("All files", "*.*"),)


def time_consuming_func():
    time.sleep(10)
    print("DONE")

class NotebookTab(ttk.Frame):
    def __init__(
        self,
        master: ttk.Notebook,
        io_callbacks: Tuple[Any, Any],
        **kwargs: Any,
    ) -> None:
        if kwargs:
            super().__init__(master, **kwargs)
        else:
            super().__init__(master)
        self.ipath = tk.StringVar(self)
        self.opath = tk.StringVar(self)
        self.status = tk.StringVar(self, value="Idle")
        assert len(io_callbacks) == 2
        self.input_callback: Any = io_callbacks[0]
        self.output_callback: Any = io_callbacks[1]
        self.create_widgets()

    def open_input(self) -> None:
        if self.input_callback == fd.askopenfilename:
            _input = self.input_callback(filetypes=FILETYPES)
        else:
            _input = self.input_callback()
        if _input:
            path = os.path.abspath(_input)
            self.ipath.set(path)

    def open_output(self) -> None:
        if self.output_callback == fd.asksaveasfilename:
            _output = self.output_callback(filetypes=FILETYPES)
        else:
            _output = self.output_callback()
        if _output:
            path = os.path.abspath(_output)
            self.opath.set(path)

    def convert(self) -> None:
        inpath = self.ipath.get()
        outpath = self.opath.get()

        self.status.set("Processing")

        #import pdb; pdb.set_trace()

        try:
            time_consuming_func()
            # Set status to done and clear boxes
            #self.status.set("Idle")
            self.ipath.set("")
            self.opath.set("")
        except Exception:
            self.status.set("ERROR")
            showerror(
                title="Error",
                message="An unexpected error occurred."
                "Close window or press OK to view traceback",
            )
            raise

    def create_widgets(self) -> None:

        statuslbl = tk.Label(self, text="Status:")
        statuslbl.place(relx=0.7, rely=0.7, anchor="e")
        self.statusval = tk.Label(self, textvariable=self.status)
        self.statusval.place(relx=0.85, rely=0.7, anchor="e")

        inputpath = tk.Entry(self, textvariable=self.ipath)
        inputpath.update()
        inputpath.focus_set()
        inputpath.place(y=10, x=10, relwidth=0.70, height=20)

        outputpath = tk.Entry(self, textvariable=self.opath)
        outputpath.update()
        outputpath.focus_set()
        outputpath.place(y=50, x=10, relwidth=0.70, height=20)

        # Buttons
        open_input_button = ttk.Button(self, text="Input", command=self.open_input)
        open_output_button = ttk.Button(self, text="Output", command=self.open_output)
        convert_button = ttk.Button(self, text="Convert", command=self.convert)

        open_input_button.pack(anchor="e", padx=20, pady=10)
        open_output_button.pack(anchor="e", padx=20, pady=10)
        convert_button.place(relx=0.3, rely=0.7, anchor=tk.CENTER)


def main() -> None:

    # Root window
    root = tk.Tk()
    root.title("Converter")
    root.resizable(True, True)
    root.geometry(WINDOW_SIZE)

    tab_parent = ttk.Notebook(root)

    file_tab = NotebookTab(
        tab_parent,
        (fd.askopenfilename, fd.asksaveasfilename),
    )
    dir_tab = NotebookTab(
        tab_parent,
        (fd.askdirectory, fd.askdirectory),
    )

    tab_parent.add(file_tab, text="File")
    tab_parent.add(dir_tab, text="Directory")
    tab_parent.pack(expand=1, fill="both")

    root.mainloop()


if __name__ == "__main__":
    main()

您可以在 convert 函数中的 try 和 except 之前添加 self.update()

问题是当 self.status.set() 被执行时,它完美地完成了它的工作,但是你使用了 time.sleep(10) 导致整个 python 解释器延迟,这可以通过使用来解决.after 方法。

self.update() 之所以有效,是因为它在执行 time.sleep() 之前更新了 window,因此更新了整个 window.

熟悉 threading 的概念。我在第 8 行和第 65 行进行了更改。但是,这意味着您的 GUI 在启动线程后是反应式的,这会带来一些风险。

import os
import time
import tkinter as tk
from tkinter import filedialog as fd
from tkinter import ttk
from tkinter.messagebox import showerror
from typing import Any, Tuple
from threading import Thread

WINDOW_SIZE = "550x300"

FILETYPES = (("All files", "*.*"),)


def time_consuming_func():
    time.sleep(10)
    print("DONE")

class NotebookTab(ttk.Frame):
    def __init__(
        self,
        master: ttk.Notebook,
        io_callbacks: Tuple[Any, Any],
        **kwargs: Any,
    ) -> None:
        if kwargs:
            super().__init__(master, **kwargs)
        else:
            super().__init__(master)
        self.ipath = tk.StringVar(self)
        self.opath = tk.StringVar(self)
        self.status = tk.StringVar(self, value="Idle")
        assert len(io_callbacks) == 2
        self.input_callback: Any = io_callbacks[0]
        self.output_callback: Any = io_callbacks[1]
        self.create_widgets()

    def open_input(self) -> None:
        if self.input_callback == fd.askopenfilename:
            _input = self.input_callback(filetypes=FILETYPES)
        else:
            _input = self.input_callback()
        if _input:
            path = os.path.abspath(_input)
            self.ipath.set(path)

    def open_output(self) -> None:
        if self.output_callback == fd.asksaveasfilename:
            _output = self.output_callback(filetypes=FILETYPES)
        else:
            _output = self.output_callback()
        if _output:
            path = os.path.abspath(_output)
            self.opath.set(path)

    def convert(self) -> None:
        inpath = self.ipath.get()
        outpath = self.opath.get()

        self.status.set("Processing")

        #import pdb; pdb.set_trace()

        try:
            t = Thread(target=time_consuming_func)
            t.start()
            # time_consuming_func()
            # Set status to done and clear boxes
            #self.status.set("Idle")
            self.ipath.set("")
            self.opath.set("")
        except Exception:
            self.status.set("ERROR")
            showerror(
                title="Error",
                message="An unexpected error occurred."
                "Close window or press OK to view traceback",
            )
            raise

    def create_widgets(self) -> None:

        statuslbl = tk.Label(self, text="Status:")
        statuslbl.place(relx=0.7, rely=0.7, anchor="e")
        self.statusval = tk.Label(self, textvariable=self.status)
        self.statusval.place(relx=0.85, rely=0.7, anchor="e")

        inputpath = tk.Entry(self, textvariable=self.ipath)
        inputpath.update()
        inputpath.focus_set()
        inputpath.place(y=10, x=10, relwidth=0.70, height=20)

        outputpath = tk.Entry(self, textvariable=self.opath)
        outputpath.update()
        outputpath.focus_set()
        outputpath.place(y=50, x=10, relwidth=0.70, height=20)

        # Buttons
        open_input_button = ttk.Button(self, text="Input", command=self.open_input)
        open_output_button = ttk.Button(self, text="Output", command=self.open_output)
        convert_button = ttk.Button(self, text="Convert", command=self.convert)

        open_input_button.pack(anchor="e", padx=20, pady=10)
        open_output_button.pack(anchor="e", padx=20, pady=10)
        convert_button.place(relx=0.3, rely=0.7, anchor=tk.CENTER)


def main() -> None:

    # Root window
    root = tk.Tk()
    root.title("Converter")
    root.resizable(True, True)
    root.geometry(WINDOW_SIZE)

    tab_parent = ttk.Notebook(root)

    file_tab = NotebookTab(
        tab_parent,
        (fd.askopenfilename, fd.asksaveasfilename),
    )
    dir_tab = NotebookTab(
        tab_parent,
        (fd.askdirectory, fd.askdirectory),
    )

    tab_parent.add(file_tab, text="File")
    tab_parent.add(dir_tab, text="Directory")
    tab_parent.pack(expand=1, fill="both")

    root.mainloop()


if __name__ == "__main__":
    main()

编辑

如果您希望部分代码在启动线程之前执行,而其他部分在线程完成后执行,我建议将这些部分(例如清除条目)放在 time_consuming_func() 的末尾。您还有其他选择,例如从线程中获取 returns 并处理它们,这有点复杂。