I'm working on a GUI for a simulation software and need to have two processes running at the same time. One for the simulation, and one for displaying information for the user. BOTH threads need to make changes to the GUI. When I run my code, I get the error: RuntimeError: main thread is not in main loop.
After some research, I think that there is no way you can access the GUI from a different thread than the main thread. I also think that I probably should use Queues, but I am not sure how to do this.
Here's my simplified code:
import tkinter as tk
from tkinter import END
import threading
import time
def write_to_entry1():
for i in range(20):
entry1.delete(0, END)
entry1.insert(0, i)
entry1.update()
time.sleep(1)
def write_to_entry2():
for i in range(20):
entry2.delete(0, END)
entry2.insert(0, i)
entry2.update()
time.sleep(1)
# Creating the GUI
root = tk.Tk()
root.geometry("200x100")
label1 = tk.Label(root)
label1.configure(text="Thread 1:")
label1.grid(column='0', row='0')
entry1 = tk.Entry(root)
entry1.grid(column='1', row='0')
label2 = tk.Label(root)
label2.configure(text="Thread 2:")
label2.grid(column='0', row='1')
entry2 = tk.Entry(root)
entry2.grid(column='1', row='1')
root.update()
t = threading.Thread(target=write_to_entry2)
t.daemon = True
t.start()
write_to_entry1()
root.mainloop()
Thanks for any answers.
Related
I'm working on a webscraping code, and the process is quite long. Thus, I would like to start and stop it in a fancy way, with a button using tkinter. But I couldn't find any solution on the web.
The .after() function doesn't seems to be a good option as I don't want to restart my loop each time. My loop is already working based on urls readed from another document and parsing for each.
I tried the thread option with .join(). But couldn't manage to solve my problem with it. But maybe I don't write my code correctly.
My problem can be formulate as follow:
Start → Read urls doc and launch parsing loop.
Stop → Stop parsing and save content.
For instance I would like something like the following code to work:
from tkinter import *
flag=False
def test_loop():
while flag==True:
print("True")
while flag==False:
print("False")
def start():
global flag
flag=True
test_loop()
def stop():
global flag
flag=False
test_loop()
root = Tk()
root.title("Test")
root.geometry("500x500")
app = Frame(root)
app.grid()
start = Button(app, text="Start", command=start)
stop = Button(app, text="Stop", command=stop)
start.grid()
stop.grid()
root.mainloop()
Just do this:
while links and not stopped:
# scraping here
You can control the stopped bool flag with your button.
You need to import threading library as python takes only one command at a time so threading will create multiple threads to handle multiple tasks simultaneously.
This is the updated code:
from tkinter import *
import threading
flag=False
def test_loop():
while flag==True:
print("True")
if flag==False:
print("False")
def start():
global flag
flag=True
thread.start() #thread start
def stop():
global flag
flag=False
thread.join() #thread stop
#this is the point where you will create a thread
thread=threading.Thread(target=test_loop)
root = Tk()
root.title("Test")
root.geometry("500x500")
app = Frame(root)
app.grid()
start = Button(app, text="Start", command=start)
stop = Button(app, text="Stop", command=stop)
start.grid()
stop.grid()
root.mainloop()
Ok I managed to solve the problem, using a flag system indeed.
from tkinter import *
import threading
def st1():
global go1
global go2
go1=True
while go1==True:
go2=False
print('Start')
def st2():
global go1
global go2
go2=True
while go2==True:
go1=False
print('Stop')
def thread1():
threading.Thread(target=st1).start()
def thread2():
threading.Thread(target=st2).start()
root = Tk()
root.title("Test")
root.geometry("500x500")
app = Frame(root)
app.grid()
start = Button(app, text="Start", command=thread1)
stop = Button(app, text="Stop", command=thread2)
start.grid()
stop.grid()
root.mainloop()
Thanks for your help it was helpfull.
I have went through multiple solutions on the net, but they require a lot of code that might get confusing once you scale up. Is there a simple way to stop the thread and avoid the RuntimeError: threads can only be started once, in order to call the thread an infinite number of times. Here is a simple version of my code:
import tkinter
import time
import threading
def func():
entry.config(state='disabled')
label.configure(text="Standby for seconds")
time.sleep(3)
sum = 0
for i in range(int(entry.get())):
time.sleep(0.5)
sum = sum+i
label.configure(text=str(sum))
entry.config(state='normal')
mainwindow = tkinter.Tk()
mainwindow.title('Sum up to any number')
entry = tkinter.Entry(mainwindow)
entry.pack()
label = tkinter.Label(mainwindow, text = "Enter an integer",font=("Arial",33))
label.pack()
print(entry.get())
button = tkinter.Button(mainwindow, text="Press me", command=threading.Thread(target=func).start)
button.pack()
It is possible to call modifications on tkinter widgets from other threads, and they will occur as soon as the main thread is available, which may be immediately. If the background thread calling the modifications sleeps while the main thread is only in mainloop, we can simulate a pause in the app without blocking on the main thread as the question aims for.
Then we can subclass Thread to produce a thread that runs its own loop and remains started even after its target finishes so that we can call its target as many times as we like. We can even pass errors that occur on the background thread through and gracefully exit the thread without hanging the app by using daemon mode and a try-except block.
The BooleanVar thread.do acts as a switch that we can set in a lambda to run func once on the thread when button is pressed. This implements a cheap messaging system between the main and background threads which we could extend with little extra code to allow calling func with arguments and returning values from it.
import threading, time, tkinter, sys
class ImmortalThread(threading.Thread):
def __init__(self, func):
super().__init__(daemon=True)
self.func = func
self.do = tkinter.BooleanVar()
def run(self):
while True:
if self.do.get():
try:
self.func()
self.do.set(False)
except:
print("Exception on", self, ":", sys.exc_info())
raise SystemExit()
else:
# keeps the thread running no-ops so it doesn't strip
time.sleep(0.01)
def func():
entry.config(state='disabled')
label.configure(text="Standby for seconds")
time.sleep(3)
sum = 0
for i in range(int(entry.get())):
time.sleep(0.5)
sum = sum+i
label.configure(text=str(sum))
entry.config(state='normal')
mainwindow = tkinter.Tk()
mainwindow.title("Sum up to any number")
entry = tkinter.Entry(mainwindow)
entry.pack()
label = tkinter.Label(mainwindow, text="Enter an integer", font=("Arial", 33))
label.pack()
thread = ImmortalThread(func)
thread.start()
button = tkinter.Button(mainwindow, text="Press me", command=lambda: thread.do.set(True))
button.pack()
mainwindow.mainloop()
While this is a simple way that get things done, in a fewer lines of code. I couldn't use the .join() method. But, it seems that the app isn't creating any new threads. This is obvious through threading.active_counts() method. Here is the code below:
import tkinter, threading, time
def calc():
entry.config(state='disabled')
label.configure(text="Standby for 3 seconds")
time.sleep(3)
sum = 0
for i in range(int(entry.get())):
time.sleep(0.5)
sum = sum+i
labelnum.configure(text=str(sum))
button.config(state='normal')
label.configure(text="Sum up to any number")
entry.config(state='normal')
def func():
t = threading.Thread(target=calc)
t.start()
#del t
print('Active threads:',threading.active_count())
mainwindow = tkinter.Tk()
mainwindow.title('Sum up to any number')
entry = tkinter.Entry(mainwindow)
entry.pack()
label = tkinter.Label(mainwindow, text = "Enter an integer",font=("Arial",33))
label.pack()
labelnum = tkinter.Label(mainwindow, text="",font=('Arial',33))
labelnum.pack()
button = tkinter.Button(mainwindow, text="Press me", command=func)
button.pack()
mainwindow.mainloop()
im working on a selenium project using tkinter in python. I want to execute many selenium task at one time(could be up to 100), so I used threading to accomplish this goal, but I came to a problem, due to the fact that I need all individual selenium task to wait a couple seconds between instructions, I used the '.after' method but for some reason my program is still freezing up, I've done a lot of research but I cant seem to find the answer yet. Isn't there a way to use tkinter, threading and some sort of sleeping in python? Do I need to switch to multiprocessing? but I would need all selenium processes to finish within the same 2 minutes and each process takes about a minute.(e.g lets say i start 100, 1 minute processes at 6:00 all task would need to be finished by 6:02)
I created minimal code which mimics my selenium script, so its easier to read, here it is:
from tkinter import *
import tkinter as tk
import time
root = tk.Tk()
root.geometry('700x700')
import threading
class Make:
def __init__(self,num):
self.num = num.get()
Label(root,text='HELLO WORLD WILL PRINT: '+str(self.num)+' times, press go to confirm').pack()
Button(root, text='go', command=lambda: self.starting()).pack()
def starting(self):
for count in range(self.num):
t = threading.Thread(target=gogogo())
t.start()
def gogogo():
tk.Label(root,text='HELLO WORLD').pack()
root.after(2000,print('Please wait 2 seconds'))
tk.Label(root,text='You are inside my world').pack()
Label(root,text='How many times should I print: HELLO WORLD').pack()
num = IntVar()
Entry(root, textvariable=num).pack()
Button(root, text='Start', command=lambda: Make(num)).pack()
root.mainloop()
You main problem is that after() and Thread() similar to command= needs only function's name without () and it will later use () to execute it.
Other problem is that after() doesn't stop code but it only send information to mainloop to execute function after 2000ms and later Python runs at once next line after after(). You have to add Label in fucntion executed by after()
def gogogo():
tk.Label(root, text='HELLO WORLD').pack()
print('Please wait 2 seconds')
root.after(2000, next_message) # function's name without ()
def next_message():
tk.Label(root, text='You are inside my world').pack()
# from tkinter import * # PEP8: `import *` is not preferred
import tkinter as tk
import threading
import time
class Make:
def __init__(self, num):
self.num = num.get()
text = 'HELLO WORLD WILL PRINT: {} times, press go to confirm'.format(self.num)
tk.Label(root, text=text).pack()
tk.Button(root, text='go', command=self.starting).pack()
def starting(self):
for count in range(self.num):
t = threading.Thread(target=gogogo) # function's name without ()
t.start()
def gogogo():
tk.Label(root, text='HELLO WORLD').pack()
print('Please wait 2 seconds')
root.after(2000, next_message) # function's name without ()
def next_message():
tk.Label(root, text='You are inside my world').pack()
# --- main ---
root = tk.Tk()
root.geometry('700x700')
tk.Label(root, text='How many times should I print: HELLO WORLD').pack()
num = tk.IntVar()
tk.Entry(root, textvariable=num).pack()
tk.Button(root, text='Start', command=lambda:Make(num)).pack()
root.mainloop()
EDIT: Because gogogo() runs in separated thread so you can also use time.sleep() because it doesn't block mainloop() in main thread
def gogogo():
tk.Label(root, text='HELLO WORLD').pack()
print('Please wait 2 seconds')
time.sleep(2)
tk.Label(root, text='You are inside my world').pack()
I'm programming some drives with python using Tkinter as GUI. When my machine is running, I'd like to show the user a toplevel window with some information which should close itself after the function completes. This is my minimal example:
from Tkinter import *
import time
def button_1():
window = Toplevel()
window.title("info")
msg = Message(window, text='running...', width=200)
msg.pack()
time.sleep(5.0)
window.destroy()
master = Tk()
frame = Frame(width=500,height=300)
frame.grid()
button_one = Button(frame, text ="Button 1", command = button_1)
button_one.grid(row = 0, column = 0, sticky = W + E)
mainloop()
The main problem is, that the toplevel window just appears after 5 seconds are over. Any suggestions?
Thanks!
time.sleep(5) is launched before the GUI has time to update, that's why the toplevel only appears after the 5 seconds are over. To correct this, you can add window.update_idletasks() before time.sleep(5) to force the update the display.
But, as Bryan Oakley points out in his answer, the GUI is frozen while time.sleep(5) is executed. I guess that your ultimate goal is not to execute time.sleep but some time consuming operation. So, if you do not want to freeze the GUI but do not know how long the execution will take, you can execute your function in a separated thread and check regularly whether it is finished using after:
import Tkinter as tk
import time
import multiprocessing
def function():
time.sleep(5)
def button_1():
window = tk.Toplevel(master)
window.title("info")
msg = tk.Message(window, text='running...', width=200)
msg.pack()
thread = multiprocessing.Process(target=function)
thread.start()
window.after(1000, check_if_running, thread, window)
def check_if_running(thread, window):
"""Check every second if the function is finished."""
if thread.is_alive():
window.after(1000, check_if_running, thread, window)
else:
window.destroy()
master = tk.Tk()
frame = tk.Frame(width=500,height=300)
frame.grid()
button_one = tk.Button(frame, text ="Launch", command=button_1)
button_one.grid(row = 0, column = 0, sticky = "we")
master.mainloop()
A general rule of thumb is that you should never call sleep in the thread that the GUI is running in. The reason is that sleep does exactly what it says, it puts the whole program to sleep. That means that it is unable to refresh the window or react to any events.
If you want to do something after a period of time, the correct way to do that is with after. For example, this will destroy the window after five seconds:
window.after(5000, window.destroy)
I need an application that basically runs a progress bar for a few seconds, and then closes itself down. I used this as an example whilst adapting it first for Python 3.4 and then for my own application. However, due to the way I've structured my code, it will first run the thread and it's tasks to it's completion and only then display the programme. This is very problematic for me, and I don't see a way around it when using classes.
from tkinter import ttk as ttk
from tkinter import *
import threading
import time
class App:
def afterLoading(self):
print('Loading finished')
def process(self,master):
time.sleep(2)
print('Thread Done')
self.afterLoading()
def __init__(self, master):
print()
master.geometry("1270x800")
master.resizable(0,0)
t1 = threading.Thread(target=self.process, args=(master,))
t1.start()
self.loadingFrame(master)
t1.join()
def loadingFrame(self, master):
frame = Frame(master, width=500, height=300)
frame.pack(side=BOTTOM, pady=50)
self.bar = ttk.Progressbar(frame, orient='horizontal', mode = 'indeterminate')
self.bar.pack(fill=BOTH)
self.bar.start(50)
self.loadingLabel = Label(frame, text="Please wait whilst the programme initializes.")
self.loadingLabel.pack()
root = Tk()
b = App(root)
root.mainloop()
Well, with your example code, you can just remove the call to t1.join() to get the behavior you want. That way, you'll be able to start the Tk event loop immediately after starting the background thread, which means your GUI can actually start up while the thread runs in the background. Using the t1.join() call prevents root.mainloop() from executing until the thread is complete, which means your GUI won't display until the thread is complete, either.