I have this small code that execute a function and in the meanwhile shows an indeterminate tkinter progressbar. When you let the program execute everything works fine while if you try to stop the process by closing the tkinter window you get the RuntimeError: main thread is not in main loop. I understand that the solution could be to bring the bar mainloop in the main thread or alternatively use queue but it is not clear to me how to do this. Here you can find the code with a simple mock function (addit) that executes. Thank you everyone in advance!
import threading
importing tkinter module
import tkinter
import tkinter.ttk as ttk
from tkinter import messagebox
from tkinter import Button
from tkinter import Label
import sys
import time
class tkwindow(threading.Thread):
def __init__(self):
threading.Thread.__init__(self, daemon=True)
def run(self):
self.wbar=tkinter.Tk()
self.wbar.attributes("-topmost", True)
self.wbar.title('Tool')
lb=Label(self.wbar,text='Check in progress')
lb.pack()
pbar = ttk.Progressbar(self.wbar,orient='horizontal', length=500, mode='indeterminate')
pbar.pack(pady=25)
pbar.start(50)
self.wbar.protocol("WM_DELETE_WINDOW", self.on_closing)
self.loopmain()
def loopmain(self):
self.wbar.mainloop()
def quitall(self):
self.wbar.quit()
sys.exit()
def on_closing(self):
if messagebox.askokcancel("Quit", "Do you want to quit?"):
self.wbar.quit()
sys.exit()
def main():
mygui=tkwindow()
mygui.start()
addit(2,3)
mygui.quitall()
def addit(a,b):
time.sleep(3)
print(a+b)
return
if __name__=='__main__':
main()
The structure of the code is only wrong. You should inherit tk.Tk and not threading.Thread. Instead of creating a function for the mainloop, just insert it at the bottom of the run() function (use self instead of self.wbar). Initialize the thread at the start of the run() function. In the main() function you have called start but in the class you defined it as run(). Here is your code if you added the above changes and fixed all errors:
import threading
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import messagebox
from tkinter import Button
from tkinter import Label
import sys
import time
class tkwindow(tk.Tk):
def __init__(self):
super().__init__()
def run(self):
threading.Thread.__init__(self, daemon=True)
self.attributes("-topmost", True)
self.title('Tool')
lb=Label(self, text='Check in progress')
lb.pack()
pbar = ttk.Progressbar(self, orient='horizontal', length=500, mode='indeterminate')
pbar.pack(pady=25)
pbar.start(50)
self.protocol("WM_DELETE_WINDOW", self.on_closing)
self.mainloop()
def quitall(self):
self.quit()
sys.exit()
def on_closing(self):
if messagebox.askokcancel("Quit", "Do you want to quit?"):
self.quitall()
def main():
mygui=tkwindow()
mygui.run()
addit(2,3)
mygui.quitall()
def addit(a,b):
time.sleep(3)
print(a+b)
return
if __name__=='__main__':
main()
There is no way to put the progress bar into the thread. Even if you could, it wouldn't be of any use since it would make the code very complex. So just use this as an alternative.
Updated Code
import threading
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import messagebox
from tkinter import Button
from tkinter import Label
import sys
import time
class tkwindow(tk.Tk):
def __init__(self):
super().__init__()
def run(self):
threading.Thread.__init__(self, daemon=True)
self.attributes("-topmost", True)
self.title('Tool')
lb=Label(self, text='Check in progress')
lb.pack()
pbar = ttk.Progressbar(self, orient='horizontal', length=500, mode='indeterminate')
pbar.pack(pady=25)
pbar.start(50)
self.protocol("WM_DELETE_WINDOW", self.on_closing)
def quitall(self):
self.quit()
sys.exit()
def on_closing(self):
if messagebox.askokcancel("Quit", "Do you want to quit?"):
self.quitall()
def main():
mygui=tkwindow()
mygui.run()
addit(2,3)
mygui.mainloop()
def addit(a,b):
print("ADDIT")
threading.Timer(3, lambda: print(a+b)).start()
if __name__=='__main__':
main()
Probably I have found a way to do just what I wanted using queue. Unfortunately the code kill the main thread abruptly with interrupt_main and not in a nice way as sys.exit() could do but is the only solution I have found for now. The updated code is the following:
import threading
import tkinter
import tkinter.ttk as ttk
from tkinter import messagebox
from tkinter import Button
from tkinter import Label
import sys
import time
from queue import Queue
import _thread
import os
class tkwindow(threading.Thread):
def __init__(self,dataq):
threading.Thread.__init__(self, daemon=True)
self.dataq=dataq
def run(self):
self.wbar=tkinter.Tk()
self.wbar.attributes("-topmost", True)
self.wbar.title('Tool')
lb=Label(self.wbar,text='Check in progress')
lb.pack()
pbar = ttk.Progressbar(self.wbar,orient='horizontal', length=500, mode='indeterminate')
pbar.pack(pady=25)
pbar.start(50)
self.dataq.put(0)
self.loopmain()
def loopmain(self):
self.wbar.protocol("WM_DELETE_WINDOW", self.on_closing)
self.wbar.after(100,self.checkq)
self.wbar.mainloop()
def checkq(self):
v=self.dataq.get()
if v:
if messagebox.askokcancel("Quit", "Do you want to quit?"):
self.wbar.quit()
os._exit(0) #or alternatively _thread.interrupt_main()
def quitall(self):
self.wbar.quit()
sys.exit()
def on_closing(self):
self.dataq.put(1)
self.checkq()
def main():
dataq=Queue()
mygui=tkwindow(dataq)
mygui.start()
addit(2,3)
mygui.quitall()
def addit(a,b):
time.sleep(3)
print(a+b)
return
if __name__=='__main__':
main()
Related
I have written some unittests that use Selenium and created a Tkinter GUI for it.
script.py is:
from selenium import webdriver
import unittest
class LaunchChrome(unittest.TestCase):
#classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome(r"C:\Users\USERNAME\PycharmProjects\First\Drivers\chromedriver.exe")
cls.driver.maximize_window()
cls.driver.get('https://facebook.com')
if __name__ == '__main__':
unittest.main()
interface.py is:
from tkinter import Button
from tkinter.ttk import *
import unittest
from ex import LaunchChrome
class HomeApplication:
def init_page(self):
self.buStart = Button(self, text="Start", width=60)
self.buStart.grid(row=4, column=0, columnspan=3)
self.buStart.config(command=self.start_app)
def run(self):
Launch_Chrome = unittest.TestLoader().loadTestsFromTestCase(LaunchChrome)
test_suite = unittest.TestSuite([Launch_Chrome])
unittest.TextTestRunner(verbosity=2).run(test_suite)
def start_app(self):
print("run")
self.run()
After I open the GUI and click on the "Start" button, unittests work fine but the GUI stops to respond (Not Responding):
How can I use multiprocessing is this code?
The tests are running in the main thread. The Tkinter mainloop waits for the start_app method to return, which does not happen until the tests finish execution.
To solve the problem, you can spawn a separate thread or process specifically for the tests.
For example, replace interface.py with the following:
import unittest
import tkinter as tk
import tkinter.ttk as ttk
from threading import Thread
from ex import LaunchChrome
class HomeApplication(tk.Tk):
def __init__(self):
super().__init__()
ttk.Button(self, text="Start", width=60, command=self.start_app)\
.grid(row=4, column=0, columnspan=3)
def run_tests(self):
test_suite = unittest.TestSuite([unittest.TestLoader()\
.loadTestsFromTestCase(LaunchChrome)])
unittest.TextTestRunner(verbosity=2).run(test_suite)
def start_app(self):
Thread(target=self.run_tests, daemon=True).start()
if __name__ == "__main__":
HomeApplication().mainloop()
I have a Python script that has some processes that take some time such as creating a .shp file and opening an .mxd. I've read Python Progress Bar but I would like to avoid using a external package. So far I have;
from tkinter import *
from tkinter.ttk import *
import os, arcpy
tk=Tk()
progress=Progressbar(tk,orient=HORIZONTAL,length=100,mode='determinate')
def bar():
import time
os.startfile("Assignment.mxd")
progress['value']=20
tk.update_idletasks()
time.sleep(8)
progress['value']=50
tk.update_idletasks()
time.sleep(8)
progress['value']=80
tk.update_idletasks()
time.sleep(8)
progress['value']=100
progress.pack()
Button(tk,text='Open',command=bar).pack()
mainloop()
It seems to be working but I don't think the process to open the .mxd is paying attention to what stage the progress bar is at. I would like to get it so that the process finishing coincides with the progress bar reaching 100%. Is there away to get the process to 'wait' for the progress bar? Could anyone offer some pointers on how I might get this to work?
Thanks
The problem is that the sleep function and possibly any other blocking function you call in your bar function blocks the GUI. A solution could be pushing the actions to a worker thread. A very quick solution I can come up with is this:
from random import randint
from threading import Thread
from tkinter import Tk, DoubleVar, HORIZONTAL
from tkinter.ttk import Frame, Button, Progressbar
from time import sleep
class Worker(Thread):
def __init__(self, reference):
super().__init__()
self.reference = reference
def run(self):
print("running...")
# replace this dummy loop with actual processing tasks
while self.reference.get_progress() < 100:
duration = randint(1, 3)
sleep(duration)
self.reference.add_progress(10 * duration)
print("finished.")
class Example(Frame):
def __init__(self, root):
super().__init__(master=root)
self.progress = DoubleVar(value=0.0)
self.worker = Worker(reference=self)
self.startbutton = Button(master=self, text="Start", command=self.start)
self.startbutton.pack()
self.progressbar = Progressbar(master=self, orient=HORIZONTAL, length=100, mode='determinate', variable=self.progress)
self.progressbar.pack()
self.infobutton = Button(master=self, text="Info", command=self.info)
self.infobutton.pack()
self.pack()
def get_progress(self):
return self.progress.get()
def set_progress(self, value):
self.progress.set(value)
def add_progress(self, value):
self.progress.set(self.progress.get() + value)
def start(self):
# print("sleeping...")
# sleep(10) # uncomment this to block the GUI
print("starting...")
self.worker.start()
def info(self):
print(self.worker.is_alive())
def main():
root = Tk()
app = Example(root)
app.mainloop()
if __name__ == '__main__':
main()
Note that the info button works before, while and after the thread runs. One problem with this quick solution is that the thread can only be run once. To run it multiple times, some kind of resetting needs to be implemented.
I am trying the following code about python:
import tkinter
from tkinter import *
from tkinter.messagebox import askokcancel
class Quitter(Frame):
def _init__(self,parent=None):
Frame.__init__(self,parent)
self.pack()
widget=Button(self,text='Quit',command=self.quit)
widget.pack(side=TOP,expand=YES,fill=BOTH)
def quit(self):
ans=askokcancel('Verify exit',"You want to quit?")
if ans:Frame.quit(self)
if __name__=='__main__':Quitter().mainloop()
When executing it, I get a frame like this:
But where is the Quit button?
As mentioned in the comments, you have a typo in __init__().
Further, you probably want to structure your app as follows: (1) not importing tkinter in the main namespace, (2) keep track of the root/parent in the tk.Frame class, and use root.destroy() i/o quit()
import tkinter as tk
from tkinter.messagebox import askokcancel
class Quitter(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.widget = tk.Button(self.parent, text='Quit', command=self.quit)
self.widget.pack()
def quit(self):
ans = askokcancel('Verify exit', "You want to quit?")
if ans:
self.parent.destroy()
if __name__ == "__main__":
root = tk.Tk()
Quitter(root)
root.mainloop()
You will find more info in this thread.
I want to create a GUI module that I can import in my main program without having to import tkinter there, letting just the module handle everything. Here's how I imagine that it might work:
main.py
import gui as g
def update():
#Update the GUI with new Data from this main program
GUI = g.gui()
gui.after(1000, update)
gui.mainloop()
gui.py
import tkinter as tk
class viewer(tk.Frame):
#Some variables
def __init__(self, parent):
tk.Frame.__init(self, parent)
self.parent = parent
self.initialize(400, 100)
def initialize(self, width, height):
#Initialize some widgets, place them on grid, etc
def start(self):
#Do some other stuff, make a main window, configurations, etc
print('Started!')
Edit: "Don't ask for opinion"
How do I make this work?
import tkinter as tk
import gui as g
root = tk.Tk()
GUI = g.gui(root)
GUI.after(1000, update)
GUI.mainloop()
The above is what I don't want.
I used a workaround that seemed plausible to me:
main.py
import gui
GUI = gui.start()
GUI.after(1000, update)
GUI.mainloop()
gui.py
import tkinter as tk
def start():
root = tk.Tk()
run = viewer(root) # <- The class provided above
return run
I want open a splash window using threading and show loading files page.
I write a simple code to open a new window and close after 1 second:
import Tkinter as tk
from threading import Thread as thread
import time
class T():
def det(self):
self.x = tk.Tk()
self.x.mainloop()
def det2(self):
self.x.destroy()
k = T()
ts = thread(target=k.det, args=())
ts.start()
time.sleep(1)
k.det2()
when try to run, all steps is ok but this code never finished! in self.x.destroy() it hanged. Why this happed?
for something as simple as this you don't actually need threading, just use the after function:
import Tkinter as tk
root = tk.Tk()
root.after(1000, root.destroy)
root.mainloop()
Use __init__ to create self.x outside det:
import Tkinter as tk
from threading import Thread as thread
import time
class T():
def __init__(self):
self.x = tk.Tk()
def det(self):
self.x.mainloop()
def det2(self):
self.x.destroy()
k = T()
ts = thread(target=k.det, args=())
ts.start()
time.sleep(1)
k.det2()
After many searchs, I found this solution. Tk instance should not be created in the thread (mainloop sholud be outside of thread), but it can close in thread:
import Tkinter as tk
from threading import Thread as thread
import time
class T():
def det2(self,x):
time.sleep(2)
x.destroy()
x = tk.Tk()
ts = thread(target=T().det2, args=(x,))
ts.daemon = True
ts.start()
x.mainloop()