I m sorry if it's a little confusing I will try to explain as simple as possible way, I have been trying to solve this threading issue for the past 2 days.
In short, what I m doing is running a big script on a separate thread once I hit the run button from the first screen, it reads data from the JSON file I have stored in the directory and Run a job.
Which gradually after completing steps increases the progress bar value and updates the label status from the run_script function.
And the error is after completing the first cycle when it comes back to first screen and if I run another job it doesnt work because of progressbar and label.
What I've tried so far is destroying the window from script_function and recreating it on the first screen. (2nd job worked but it didn't destroy the first job window, and after completing 2nd job it completely shutdown both windows)
without mttkinter, I got tcl run time error because I m trying to destroy the window from another thread.
from mttkinter import mtTkinter as tk
from tkinter import ttk
import threading
#other imports
window = Tk()
def screen1():
##show the screen with json data labels and buttons#
window.geometry("1215x770")
window.configure(bg="#FFFFFF")
b0 = Button(window, text='Run job', command=do_something) #samplebutton
window.resizable(False, False)
window.mainloop()
def do_something():
##running big script with progressbar and status label.
window.geometry("1215x770")
window.configure(bg="#FFFFFF")
progress = ttk.Progressbar(window, style='red.Horizontal.TProgressbar', orient=HORIZONTAL,
length=443, mode='determinate')
progress.pack()
label = Label("Completed tables 0/{totaltables}") #samplelabel
label.pack()
if data['foo'] == 'bar':
threading.Thread(target=run_script, args=(arg1, arg2,)).start() #run script in seperate thread this script also has a function that runs on multiple threads.
window.resizable(False, False)
window.mainloop()
def run_script(arg1, arg2):
#running the script#
for i in range(5): #running each i on new thread followed by starting and joining.
print(i)
#main problem#
#once script finishes#
response = messagebox.showinfo("Task", "Did something!")
if response:
screen1()
What is the possible way to accomplish this?
I want to keep running jobs without having to open the app again and again.
the script should be on a separate thread because if it's not the app goes not responding.
I want to keep showing the progress bar and status and once it's complete go back to the first screen.
How do I kill the main Tkinter thread after completing the first job?
or How do I destroy the Tkinter window from another thread?
If you have a tcl/tk that is built with multithreading support, and you are using Python 3, you should be able to call tk functions from a second thread.
This is an example program, where a second thread modifies widgets:
import os
import sys
import threading
import time
import tkinter as tk
import tkinter.font as tkfont
import types
__version__ = "2022.02.02"
# Namespace for widgets that need to be accessed by callbacks.
widgets = types.SimpleNamespace()
# State that needs to be accessed by callbacks.
state = types.SimpleNamespace()
def create_widgets(root, w):
"""
Create the window and its widgets.
Arguments:
root: the root window.
w: SimpleNamespace to store widgets.
"""
# General commands and bindings
root.wm_title("tkinter threading v" + __version__)
root.columnconfigure(2, weight=1)
root.rowconfigure(3, weight=1)
root.resizable(False, False)
# First row
tk.Label(root, text="Thread status: ").grid(row=0, column=0, sticky="ew")
w.runstatus = tk.Label(root, text="not running", width=12)
w.runstatus.grid(row=0, column=1, sticky="ew")
# Second row
tk.Label(root, text="Timer: ").grid(row=1, column=0, sticky="ew")
w.counter = tk.Label(root, text="0 s")
w.counter.grid(row=1, column=1, sticky="ew")
# Third row
w.gobtn = tk.Button(root, text="Go", command=do_start)
w.gobtn.grid(row=2, column=0, sticky="ew")
w.stopbtn = tk.Button(root, text="Stop", command=do_stop, state=tk.DISABLED)
w.stopbtn.grid(row=2, column=1, sticky="ew")
def initialize_state(s):
"""
Initialize the global state.
Arguments:
s: SimpleNamespace to store application state.
"""
s.worker = None
s.run = False
s.counter = 0
def worker():
"""
Function that is run in a separate thread.
This function *does* update tkinter widgets. In Python 3, this should be
safe if a tkinter is used that is built with threads.
"""
# Initialization
widgets.runstatus["text"] = "running"
# Work
while state.run:
time.sleep(0.25)
state.counter += 0.25
widgets.counter["text"] = f"{state.counter:.2f} s"
# Finalization
state.counter = 0.0
widgets.counter["text"] = f"{state.counter:g} s"
widgets.runstatus["text"] = "not running"
def do_start():
"""Callback for the “Go” button"""
widgets.gobtn["state"] = tk.DISABLED
widgets.stopbtn["state"] = tk.NORMAL
state.run = True
state.worker = threading.Thread(target=worker)
state.worker.start()
def do_stop():
"""Callback for the “Stop” button"""
widgets.gobtn["state"] = tk.NORMAL
widgets.stopbtn["state"] = tk.DISABLED
state.run = False
state.worker = None
# Main program starts here.
if __name__ == "__main__":
# Detach from the command line on UNIX systems.
if os.name == "posix":
if os.fork():
sys.exit()
# Initialize global state
initialize_state(state)
# Create the GUI window.
root = tk.Tk(None)
# Set the font
default_font = tkfont.nametofont("TkDefaultFont")
default_font.configure(size=12)
root.option_add("*Font", default_font)
create_widgets(root, widgets)
root.mainloop()
Note how the worker thread monitors state.run and exits when it becomes False.
In general I find using multiple top-level windows confusing, so I don't use that pattern.
I would rather create a grid with rows of a progressbar and stop button for every thread.
Related
here is my sample code:
from time import sleep
import tkinter as tk
import threading
class Action:
counter = 0
def do_something(self):
while True:
print('Looping')
sleep(5)
action = Action()
root = tk.Tk()
button = tk.Button(root, text='pressme harder', command=threading.Thread(target=action.do_something()).start())
button.grid(row=1, column=0)
root.mainloop()
What am I expecting?
I'm expecting that as soon as I click the button in the UI an new thread is running, which is looping in the background and does not interfere with the UI (or later maybe other threads doing tasks in the background)
What is really happening?
When running the code, the method of the class is executeds immdiately and locking the procedure. root.mainloop() is never reached and therefore no UI is drawn
Alternatively I tried the following change:
button = tk.Button(root, text='pressme harder', command=threading.Thread(target=lambda: action.do_something()).start())
This behaves in the following (imho wrong) way:
The method is also called immediately, without pressing the button. This time the UI is drawn but seems the be locked by the thread (UI is slow/stuttering, pressing the buttom does not work most of the time)
Any Idea whats wrong there? Or how do I handle this in a more stable way?
You shouldn't try to start a thread directly in the button command. I suggest you create another function that launches the thread.
from time import sleep
import tkinter as tk
import threading
class Action:
counter = 0
def do_something(self):
while True:
print('Looping')
sleep(2)
print("Finished looping")
def start_thread(self):
thread = threading.Thread(target=self.do_something, daemon=True)
thread.start()
action = Action()
root = tk.Tk()
button = tk.Button(root, text='pressme harder', command=action.start_thread)
button.grid(row=1, column=0)
root.mainloop()
I am trying to create a new window in tkinter and then execute the function after that but the function gets executed first and then the new window pops up.
Here is the code snippet :
import tkinter as tk
import time
def loop():
for i in range(5):
time.sleep(1)
print(i)
def new():
new = tk.Toplevel(window)
new.geometry("450x250")
new.title('new window')
tk.Label(new, text="new one").place(x=150, y=40)
loop()
window = tk.Tk()
window.geometry("450x250")
window.title('main window')
button = tk.Button(window , text='button', width=20,command= new)
button.place(x=180,y=200)
window.mainloop()
numbers are printed from 1 to 9 and then the new window pops up.
Even if it requires destroying the main window, it's fine.
A little help would be appreciated.
Thank You.
Tkinter has two universal widget methods that will help do what you want. One is called wait_visibility() which will pause the program until the new Toplevel is visible. The second is called after() which allows the scheduling of call to a function after a delay specified in milliseconds. If the called function does the same thing, effectively it creates a loop.
Here's how to use them:
import tkinter as tk
import time
def loop(limit, i):
if i < limit:
print(i)
window.after(1000, loop, limit, i+1)
def new():
new = tk.Toplevel(window)
new.geometry("450x250")
new.title('new window')
tk.Label(new, text="new one").place(x=150, y=40)
window.wait_visibility(new)
loop(5, 0)
window = tk.Tk()
window.geometry("450x250")
window.title('main window')
button = tk.Button(window, text='button', width=20, command=new)
button.place(x=180,y=200)
window.mainloop()
I'm trying to make a button where it will start a program. So first, a button called 'Run' will appear, subsequently after 3 seconds it should come up with a new button that says 'Stop'.
The reason I want it that way. That it's because I have tried and add two buttons on an interface panel, but the problem is then, every time I run an application, the interface freezes, so it was not possible to 'Stop' the program. So, I was wondering if it would be possible to do something like that?
What I've done:
from tkinter import *
tkWindow = Tk()
tkWindow.geometry('150x50')
tkWindow.title('Tkinter Example')
print("Tkinter button is appearing...")
def Action():
from Launch import Launch
run = Launch()
run
def Off():
import sys
sys.exit()
button = Button(tkWindow,
text='Start',
command=Action)
button1 = Button(tkWindow,
text='Stop',
command=Off)
button.pack()
button1.pack()
tkWindow.mainloop()
Try something like this:
from tkinter import *
from threading import Thread
from Launch import Launch
tkWindow = Tk()
tkWindow.geometry('150x50')
tkWindow.title('Tkinter Example')
print("Tkinter button is appearing...")
def Action():
thread = Thread(target=Launch, daemon=True)
thread.start()
# If you want to disable the button use:
# button.config(state="disabled")
button = Button(tkWindow,
text='Start',
command=Action)
# Here I am going to use the built-in `exit` function as per #Matiiss' suggestion
button1 = Button(tkWindow,
text='Stop',
command=exit)
button.pack()
button1.pack()
tkWindow.mainloop()
It starts a new thread when the "Start" button is pressed. That new thread calls Launch, leaving the main thread for tkinter. Please make sure that there isn't any references to your main GUI in your Launch function.
long time ago I started to use threading. Thread for any functions in my GUI. But last time I focused on multiprocessing module. The problem here is that when I spawn new process I don't know how to connect with my GUI.
Here is sample code - one button "execute" Thread and it works as expected.
Second button spawn Process but my GUI is not updated (input field is not filled with text).
How to fix that?
How to receive "print" statements from Process?
Thanks!
import tkinter as tk
import time
from multiprocessing import Process, Pool
from threading import Thread
root = tk.Tk()
inputEn = tk.Entry(root)
inputEn.pack()
def runner(txt):
print("runner")
time.sleep(5)
# this doesn't work when I use it with Process
inputEn.insert(0, "{}".format(txt))
print("runner end")
def process():
p = Process(target=runner, args=("process",))
p.start()
def thread():
p = Thread(target=runner, args=("thread",))
p.start()
btnStart = tk.Button(root, text="Start Process", command=process)
btnStart.pack()
btnStart2 = tk.Button(root, text="Start Thread", command=thread)
btnStart2.pack()
if __name__=="__main__":
root.mainloop()
When spawned using process, the child process cannot update the variable of the parent process.
You can experiment by modifying your code :
inputEn.pack()
globalv = 0
def runner(txt):
global globalv
print("runner")
time.sleep(5)
# this doesn't work when I use it with Process
inputEn.insert(0, "{}".format(txt))
globalv = globalv + 1
print 'globalv : ', globalv
globalv will then be updated when runner is called via thread, by not updated when runner is called via process.
You would have to use queue to make child interact with your application.
This is because a child process is instanciated using a copy of the context of the parent process - whereas a thread uses the same context as the parent.
You have not said why multiprocessing is necessary here. If you just want to update a variable, tkinter's after() works well for that. A not so elegant example
import Tkinter as tk
import random
class UpdateLabel(object):
def __init__(self, master):
self.master=master
self.str_var=tk.StringVar()
self.str_var.set("start")
tk.Entry(root, textvariable=self.str_var, width=50).pack()
tk.Button(self.master, text="Exit", bg="orange",
command=self.master.quit).pack()
self.ctr=0
self.change_it()
def change_it(self):
""" use random to simulate getting some kind of data 10 times
"""
this_lit=self.str_var.get()
next_lit=random.choice(range(100))
self.str_var.set(this_lit+", "+str(next_lit))
self.ctr += 1
if self.ctr < 10:
self.master.after(500, self.change_it)
root = tk.Tk()
UL=UpdateLabel(root)
root.mainloop()
I'm trying to put a info popup window to the user to advise him that a file is being created and that he must wait until it's created. I've a master frame that creates a popup window that shows the Progressbar with a message. This popupwindow must be destroyed as soon as the file has been created on the system.
This is my try:
import os
from Tkinter import *
import ttk
class UI(Frame):
def __init__(self,master):
Frame.__init__(self, master)
self.master = master
self.initUI()
def initUI(self):
popup = Toplevel(self)
txt = Label(popup, text="Please wait until the file is created").grid(row=0, column=0)
progressbar = ttk.Progressbar(popup, orient=HORIZONTAL, length=200, mode='indeterminate')
progressbar.grid(row=1, column=0)
progressbar.start()
self.checkfile()
progressbar.stop()
popup.destroy()
def checkfile(self):
while os.path.exists("myfile.txt") == False:
print "not created yet"
if __name__ == "__main__":
root = Tk()
aplicacion = UI(root)
root.mainloop()
The problem is that the UI get's freezed and I can't see any window. I think I must use Threads to solve this problem right? Do I've to make two threads, one for the UI and the other one for the checkfile function, or with one is enough?
It would be highly appreciated if someone could add the Threads to my code to make it work as I've never use them and I'm totally lost.
Thanks in advance.
while loop cause the UI unreponsive.
Use Widget.after instead to periodically checkfile method.
def initUI(self):
self.popup = popup = Toplevel(self)
Label(popup, text="Please wait until the file is created").grid(
row=0, column=0)
self.progressbar = progressbar = ttk.Progressbar(popup,
orient=HORIZONTAL, length=200, mode='indeterminate')
progressbar.grid(row=1, column=0)
progressbar.start()
self.checkfile()
def checkfile(self):
if os.path.exists("myfile.txt"):
print 'found it'
self.progressbar.stop()
self.popup.destroy()
else:
print 'not created yet'
self.after(100, self.checkfile) # Call this method after 100 ms.
What modified:
Used after instead of while loop.
Made progressbar, popup accessible in checkfile method by making them instance attribute.
Moved progressbar.stop, popup.destroy to checkfile method.