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.
Related
I don't understand how to use the threading module properly. In this example I have two tkinter widgets, a button and a progress bar. The progress bar (configured in indeterminate mode) has to be active when the user pushes the button, and when the task is completed, the progress bar has to be stopped.
import tkinter as tk
from tkinter import ttk
import threading, ipaddress
class MainWindow:
def __init__(self):
self.parent=tk.Tk()
self.parent.geometry("786x524+370+100")
self.parent.title("Test")
self.parent.configure(background="#f0f0f0")
self.parent.minsize(786, 524)
self.ProBar=ttk.Progressbar(self.parent, mode="indeterminate")
self.ProBar.pack(padx=(40, 40), pady=(40, 40), fill=tk.BOTH)
self.StartButton=ttk.Button(self.parent, text="Start", command=self.MyHeavyTask)
self.StartButton.pack(padx=(40, 40), pady=(40, 40), fill=tk.BOTH)
self.parent.mainloop()
# my start function:
def Start(self):
self.ProBar.start(4)
self.MyHeavyTask()
self.ProBar.stop()
# my real start function. it's just an example, it needs time to be completed:
def MyHeavyTask(self):
ls=[]
obj=ipaddress.ip_network("10.0.0.0/8")
for obj in list(obj.hosts()):
print(obj.exploded)
# start my test:
if __name__=="__main__":
app=MainWindow()
This code has an issue, it can't run the function "MyHeavyTask" and at the same time keep active the progress bar widget. to solve it, I tried to put "MyHeavyTask" in an indipendent thread changing the line 17 with this one:
self.StartButton=ttk.Button(self.parent, text="Start",
command=threading.Thread(target=self.MyHeavyTask).start())
unfortunately this solution doesn't work. when I press the button, nothig happens…why? What is the right way to use the threading module in my example?
You can add a method to the class
def Get_Input(self):
message = input(">")
if message:
send_message(message)
and add in init class
threading.Thread(target=self.Get_Input, args=(,)).start()
Please note :
If you passing one argument, you need to use
threading.Thread(target=self.Get_Input, args=(var1,)).start()
Unlike common sense :)
Here a runnable example similar to the code in your question, that shows a way to run a background task and keep a ttk.Progressbar active simultaneously. It does this by using the universal after() widget method to repeatedly schedule calls to a method that checks whether the background task is running and updates the progress bar if it is. It also disables and re-enables the Start button appropriately so the task can't be start again while it's running.
Note I strongly suggest you read and start following the PEP 8 - Style Guide for Python Code.
from random import randint
import tkinter as tk
from tkinter import ttk
import threading
from time import sleep
class MainWindow:
def __init__(self):
self.parent = tk.Tk()
self.parent.geometry("786x524+370+100")
self.parent.title("Test")
self.parent.configure(background="#f0f0f0")
self.parent.minsize(786, 524)
self.task = threading.Thread(target=self.my_heavy_task)
self.pro_bar = ttk.Progressbar(self.parent, mode="indeterminate")
self.pro_bar.pack(padx=(40, 40), pady=(40, 40), fill=tk.BOTH)
self.start_btn = ttk.Button(self.parent, text="Start", command=self.start)
self.start_btn.pack(padx=(40, 40), pady=(40, 40), fill=tk.BOTH)
self.parent.mainloop()
def check_thread(self):
if self.task.is_alive():
self.pro_bar.step() # Update progressbar.
self.parent.after(20, self.check_thread) # Call again after delay.
else:
self.pro_bar.stop()
self.start_btn.config(state=tk.ACTIVE)
def start(self):
"""Start heavy background task."""
self.start_btn.config(state=tk.DISABLED)
self.task.start()
self.pro_bar.start()
self.check_thread() # Start checking thread.
def my_heavy_task(self):
"""Slow background task."""
for obj in (randint(0, 99) for _ in range(6)):
print(obj)
sleep(.5)
if __name__=="__main__":
app = MainWindow()
Does this help?
start_thread = MainWindow()
run_test = threading.Thread(None, start_thread.start)
run_test.start()
# start my test:
if __name__=="__main__":
app=MainWindow()
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.
im trying to learn python. But i have problems with the threading. First i failed in the "Proces" class because i putted the loop on the wrong place and my program newer returned from the other class.
But now i think all is correct and it still does not work. I need to have a GUI where i want to be able to write my conditions via text entries and i need another class "Proces" that will do stuff, checking status ower internet and so on constantly or in a specified interval...
The Problem is that my tkinter GUI is freezing after pressing something
here is my GUI.py file:
import tkinter as tk
from Proces import Proces
root = tk.Tk()
frame = tk.Frame(root)
frame.pack()
button = tk.Button(frame, text="QUIT", fg="red",command=quit).pack(side=tk.LEFT)
pr = Proces()
print("\nGUI: proces init...")
pr.start()
print("\nGUI: Start ended")
root.mainloop()
here is the Proces.py file:
import time, threading
class Proces(threading.Thread):
def loop(self):
while True:
time.sleep(2)
print("\nProces: looping")
def __init__(self):
threading.Thread.__init__(self)
print("\nProces: Starting proces")
time.sleep(2)
def run(self):
self.deamon = True
print("\nProces: Starting loop")
self.loop()
*This is the output: *
Proces: Starting proces
GUI: proces init...
Proces: Starting loop
GUI: Start ended
Proces: looping
Proces: looping
Proces: looping
Proces: looping
*But the GUI of the tkinter does not react.*
How should i do this kind of task?
Thank you for your help, advice and answer
I think you don't have problem about starting thread etc. However, you should be able to control your thread, meaning your thread should return based on some condition. It seems you have a button to quit. I assume you want to finish the process by clicking the button.
To do that, when you click to button, your main thread should pass a variable to Process in order to break your while loop, which is your thread basically.
Here is a code that you can work on.
import tkinter as tk
import time
import threading
class MainWindow:
def __init__(self, master):
self.master = master
self.quit_button = tk.Button(self.master, text="QUIT", command=lambda:self.quit(), width=20)
self.quit_button.pack(side=tk.LEFT)
self.process = None
print("\nGUI: process init...")
self.start_task()
def quit(self):
self.process.stop_process = True
print("\nGUI: Start ended")
def start_task(self):
self.process = Process()
self.process.start()
class Process(threading.Thread):
def loop(self):
while True:
if not self.stop_process:
time.sleep(2)
print("\nProcess: looping")
else:
print("\nProcess: looping ended")
return
def __init__(self):
threading.Thread.__init__(self)
self.stop_process = False
print("\nProcess: Starting proces")
time.sleep(2)
def run(self):
self.deamon = True
print("\nProcess: Starting loop")
self.loop()
if __name__ == '__main__':
root = tk.Tk()
app = MainWindow(master=root)
root.mainloop()
So you start yout tkinter, which is your main thread. Then iniating another class within your main thread, which is inherits the thread. So you have two seperate thread that working. When you clicked to "quit" button, you pass variable stop_process, which breaks the loop and return, meaning ending your thread. Your main thread is still alive for running your tkinter window.
I hope it will help
I've heard that threads in Python are not easy to handle and they become more tangled with tkinter.
I have the following problem. I have two classes, one for the GUI and another for an infinite process. First, I start the GUI class and then the infinite process' class. I want that when you close the GUI, it also finishes the infinite process and the program ends.
A simplified version of the code is the following:
import time, threading
from tkinter import *
from tkinter import messagebox
finish = False
class tkinterGUI(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global finish
#Main Window
self.mainWindow = Tk()
self.mainWindow.geometry("200x200")
self.mainWindow.title("My GUI Title")
#Label
lbCommand = Label(self.mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
#Start
self.mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
class InfiniteProcess(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global finish
while not finish:
print("Infinite Loop")
time.sleep(3)
GUI = tkinterGUI()
GUI.start()
Process = InfiniteProcess()
Process.start()
When I click in the close button (in the upper right corner) the following error appears in the console:
Tcl_AsyncDelete: async handler deleted by the wrong thread
I don't know why it happens or what it means.
All Tcl commands need to originate from the same thread. Due to tkinter's
dependence on Tcl, it's generally necessary to make all tkinter gui statements
originate from the same thread. The problem occurs because
mainWindow is instantiated in the tkinterGui thread, but -- because mainWindow is an attribute of tkinterGui -- is not destroyed until tkinterGui is destroyed in the main thread.
The problem can be avoided by not making mainWindow an attribute of tkinterGui
-- i.e. changing self.mainWindow to mainWindow. This allows mainWindow to be destroyed when the run method ends in the tkinterGui thread. However, often you can avoid threads entirely by using mainWindow.after calls instead:
import time, threading
from tkinter import *
from tkinter import messagebox
def infinite_process():
print("Infinite Loop")
mainWindow.after(3000, infinite_process)
mainWindow = Tk()
mainWindow.geometry("200x200")
mainWindow.title("My GUI Title")
lbCommand = Label(mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
mainWindow.after(3000, infinite_process)
mainWindow.mainloop()
If you want to define the GUI inside a class, you can still do so:
import time, threading
from tkinter import *
from tkinter import messagebox
class App(object):
def __init__(self, master):
master.geometry("200x200")
master.title("My GUI Title")
lbCommand = Label(master, text="Hello world",
font=("Courier New", 16)).place(x=20, y=20)
def tkinterGui():
global finish
mainWindow = Tk()
app = App(mainWindow)
mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
def InfiniteProcess():
while not finish:
print("Infinite Loop")
time.sleep(3)
finish = False
GUI = threading.Thread(target=tkinterGui)
GUI.start()
Process = threading.Thread(target=InfiniteProcess)
Process.start()
GUI.join()
Process.join()
or even simpler, just use the main thread to run the GUI mainloop:
import time, threading
from tkinter import *
from tkinter import messagebox
class App(object):
def __init__(self, master):
master.geometry("200x200")
master.title("My GUI Title")
lbCommand = Label(master, text="Hello world",
font=("Courier New", 16)).place(x=20, y=20)
def InfiniteProcess():
while not finish:
print("Infinite Loop")
time.sleep(3)
finish = False
Process = threading.Thread(target=InfiniteProcess)
Process.start()
mainWindow = Tk()
app = App(mainWindow)
mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
Process.join()
The fix here is simple, but hard to discover:
Call mainWindow.quit() immediately after mainwindow.mainloop(), so that the cleanup happens on the same thread as the one that created the tk UI, rather than on the main thread when python exits.
I created a simple tkinter app, where have used two threads. Their task is to write numbers to widgets such as label and text. One thread is triggered by button (click event) and second is executed as a background thread.
import Tkinter as tk
from ttk import *
from Tkconstants import *
import threading, thread, time
def tl1(text,counter):
while True:
text.insert(END,counter)
counter += 1
time.sleep(2)
def tl2(label,counter):
while True:
label['text'] = counter
counter += 1
time.sleep(1)
class mainWindow():
def __init__(self, master):
self.master = master
self._initLayout()
def _initLayout(self):
#button
self.button = tk.Button(self.master, text="thread1_start", command = self._task1)
self.button.pack()
#label
self.label = tk.Label(self.master)
self.label.pack()
#text
self.text = tk.Text(self.master, width=30)
self.text.pack()
def _task1(self):
t1 = thread.start_new_thread(tl1,(self.text,1))
def _task2(self):
t2 = thread.start_new_thread(tl2,(self.label,1000))
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.mainWindow = mainWindow(self)
self.mainWindow._task2() #background_thread
app = App()
app.mainloop()
In this manner everything works fine, but if we change the background thread to display results on text widget, the whole app freezes.
Why background thread works fine communicating with label but causes problems with text widget? Is there any way to run it properly?
Tkinter isn't thread safe. You can only access widgets from the thread that created them. Your threads will need to put data on a thread-safe queue, and your GUI thread will need to poll the queue.
In your particular case you don't need threads at all. You can use the tkinter after method to run code periodically.