How can open new tkinter window by threading? - python

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

Related

Tkinter close protocol when tkinter is threaded

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

Initializing tkinter gui leads to main thread not in main loop

My question is regarding initializing a GUI built with tkinter. I'm starting another thread within the GUI that's running a script which takes 10 minutes to finish. The reason I'm doing this in another thread is to be able to keep the GUI responsive during these 10 minutes.
Right now I'm trying to do the following (simplified)
import tkinter as tk
from threading import Thread
class GUI:
def __init__(self):
self.master = tk.Tk()
self.master.geometry("1400x700")
//
...
//
self.master.mainloop()
def run_long_script(self): # Called by button in GUI
self.t1 = Thread(target = long_script)
self.start()
def long_script(self):
try:
...
except InterruptedError as error:
GUI()
This works okay, but when I try to close the GUI with long_script running, I get the error message main thread not in main loop. How should I design the code to be able to close the program correctly?
Tkinter like many other GUIs can use widgets only in main thread
(or rather in thread which runs mainlooop).
Other thread has to update global value (which is shared between threads) or use queue to send value to main thread. And main thread has to use after(milliseconds, function_name) to run periodically function which will get value from global variable or from queue and update progress bar.
Minimal working code.
import threading
import time
import tkinter as tk
import tkinter.ttk as ttk
# --- functions ---
def long_script():
global progress_value
for i in range(20):
print('loop:', i)
# update global variable
progress_value += 5
time.sleep(.5)
def run_long_script():
global progress_value
global t
if t is None: # run only one thread
# set start value
progress_value = 0
# start updating progressbar
update_progressbar()
# start thread
t = threading.Thread(target=long_script)
t.start()
else:
print('Already running')
def update_progressbar():
global t
# update progressbar
pb['value'] = progress_value
if progress_value < 100:
# run it again after 100ms
root.after(100, update_progressbar)
else:
# set None so it can run thread again
t = None
# --- main ---
# default value at start
progress_value = 0
t = None
# - gui -
root = tk.Tk()
pb = ttk.Progressbar(root, mode="determinate")
pb.pack()
b = tk.Button(root, text="start", command=run_long_script)
b.pack()
root.mainloop()

GUI is not responding after unittests are started

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

Python match progress bar to long process

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.

Tkinter update progressbar from imported code

I am creating a gui application using Tkinter, which imports other pieces of code stored in external .py files which contain time consuming functions. What I want to do is have a progressbar on my gui window which gets updated according to some function running in my imported script.
Gui script example:
#gui script
import tkinter
from tkinter import ttk
from somefile import somefunc
progcomp = ttk.Progressbar(root, orient='horizontal', length=200, mode = 'determinate', maximum=100)
somefunc()
External function example:
#somefile.py
def somefunc():
for i in range(1000):
#dosomething
#update progressbar of gui script
My actual code is too long to show in a question like this, so I chose to represent it as simply as possible. My question is, is this possible or will I have to change the infrastructure to accomplish?
You could use threading to implement this. Below is a very rough example of this.
threaded_task.py
import threading
import time
class ThreadedTask(threading.Thread):
def __init__(self, progress):
threading.Thread.__init__(self)
self.progress = progress
def run(self):
for i in range(100):
self.progress.step(1) # Update progress bar
time.sleep(1) # Simulate long running process
main.py
from tkinter import *
from tkinter import ttk
from threaded_task import ThreadedTask
root = Tk()
progcomp = ttk.Progressbar(root, orient='horizontal', length=200, mode = 'determinate', maximum=100)
progcomp.grid()
task = ThreadedTask(progcomp)
task.start()
root.mainloop()

Categories

Resources