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.