Home > database >  tkinter Label not getting updated before function call
tkinter Label not getting updated before function call

Time:03-01

In the below code snippet (in the convert function) the status label is not getting set to "Processing" before the time_consuming_func is run. Rather it gets updated after the function. I'm using the label to indicate the program is executing the time consuming function so this defeats the purpose of having a status label.

I noticed that if I put a breakpoint just after self.status.set("Processing") the label does get updated.

What can I do to have the status label change correctly i.e. before the 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()

CodePudding user response:

Get familiar with the concept of threading. I made changes in line 8 and 65. However, this means that your GUI is reactive after launching the thread, which comes with a couple of risks.

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()

Edit

if you want parts of your code to be executed before launching the thread, and other parts when its done, i suggest putting the parts (e.g. clearing the entry) at the end in your time_consuming_func(). You have other options, such as getting returns from the thread and handle those, which is a bit more complicated.

CodePudding user response:

You can just add self.update() before try and except in convert function.

The problem is when self.status.set() is executed, it does its job perfectly, but you have used time.sleep(10) which cause delay in whole python interpreter, which could be solved by using .after method.

self.update() works because it updates the window before executing time.sleep(), so updates the whole window.

  • Related