I'm having some issues with a Tkinter-based GUI. Basically the GUI creates lots of threads and run them. When each thread has finished, I'd like it to update a label to inform the user of this specific thread completion.
I know Tkinter widgets are not thread-safe and that it is a bad practice to allow subthreads to update the view. So I'm trying to trigger an event on the main thread so it can update the view itself.
I'm running the simplified code sample below:
from Tkinter import *
from threading import *
def myClass(root):
def __init__(self, root):
self.root = root
# Other stuff populating the GUI
# Other methods creating new 'threading.Thread'
# objects which will call 'trigger_Event'
# Called by child threads
def trigger_Event(self):
self.root.event_generate("<<myEvent>>", when="tail")
# Called by main thread
def processEvent(self):
# Update GUI label here
if __name__ == '__main__':
root = Tk()
root.geometry("500x655+300+300")
app = myClass(root)
root.bind("<<myEvent>>", app.processEvent())
root.mainloop()
Unfortunately, this does not work: processEvent is never called. What am I missing ?
root.bind("<<myEvent>>", app.processEvent())
Here, you're binding myEvent to the return value of app.processEvent, because you're calling the function rather than just referring to it. Try removing the parentheses.
root.bind("<<myEvent>>", app.processEvent)
Related
I have a windows application with a python API (supports python 3). You can provide a class that has a main function. On program startup, an instance of this class is created, and the main function is called periodically. I can configure the frequency, but nothing else. (I cannot change the way the API works)
Now, I'd like to use tkinter for some visualizations, charts, and so on. Here is an example of my current class structure:
import tkinter as tk
class Script:
def __init__(self):
self.root = tk.Tk()
self.count = 0
def main(self):
# do some stuff
print(str(self.count))
self.count += 1
# update gui
self.root.update()
# This is to simulate the software behavior.
import time
scriptInstance = Script()
for i in range(0, 20 * 10):
scriptInstance.main()
time.sleep(0.1)
The main issue I have is, that the root.update() method freezes the main-function as long as you interact with Tkinter. You can see that with the example above, that the number is not counting up as long as you are moving the window. Is there a way to prevent that?
I tried to call the root.update() form another thread, which did not work because Tkinter does not allow that.
Thank you for any help.
I've made a script that uses a while True loop to constantly update a series of variables based on UDP packets that I am constantly recieving. I want to ultimately create a GUI that displays that data and updates the screen constantly, which I plan to do with tkinter (using my_label.after in a function which then calls itself, not sure if this is a good plan).
Here is some testing scripts that I can't get to work properly:
GUI2.py (my test looping script)
import time
var = 0
while True:
var += 1
time.sleep(0.1)
GUI Testing.py (the script that would be accessing those variables)
from GUI2 import *
import time
print('never')
print(var)
time.sleep(1)
The second script never reaches the print('never') line, I think because it gets stuck in the other script's while True loop and never returns.
How should I go about this? I have one script that I want in a constant loop to update my variables to the correct values based on incoming packets, and then another script updating a tkinter window. I went this way as most examples I could find using Tkinter didn't use any sort of while True loops. Could I just put my packet recieving code inside the Tkinter mainloop, and would that effectively act as a while True?
EDIT (added Tkinter loop that I can't get working):
This opens a Tkinter window, but the label stays at 99, then reopens a window when I close it with the new x value (ie. 98, 97, etc). I want the label to update every second.
import tkinter as tk
import time
x = 99
while True:
root = tk.Tk()
label = tk.Label(root, text=x)
label.pack()
x -= 1
time.sleep(1)
root.mainloop()
Below is a sample script to show you how you can update the value in the label widget at a certain time interval. I have provided you the hyperlinks to help you understand tkinter's methods. Best regards.
Key points:
use the textvariable option of the tk.Label widget.
use tkinter's control variable. I have shown you how to set and get it's value.
you can use tkinter's widget method called .after() without having to explicitly use a while-statement and time.sleep() method. Tkinter has it's own event loop that you can use.
writing your tkinter GUI as a class makes it easier to implement what you need.
Example Script:
import tkinter as tk
class App(tk.Frame):
def __init__( self, master, *args, **kw ):
super().__init__( master )
self.master = master
self.create_label()
self.update_label()
def create_label( self ):
self.var = tk.IntVar() # Holds an int; default value 0
self.label = tk.Label(self, textvariable=self.var ) # Use textvariable not text
self.label.pack()
def update_label( self ):
value = self.get_value()
self.var.set( value ) # Set the label widget textvariable value.
self.after(1000, self.update_label) # Call this method after 1000 ms.
def get_value( self ):
'''To simulate calling a function to return a value'''
value = self.var.get() + 1
return value
if __name__ == "__main__":
root = tk.Tk()
root.geometry('100x100+0+24')
app = App( root )
app.pack()
root.mainloop() #This command activates tkinter's event loop
Edit:
As a clarification, this answer shows how to utilize the .after() and .mainloop() methods in GUI Testing.py, i.e. using tkinter event loop and not use two while-loops, to achieve what you wanted to do. This is a way to simplify your GUI script.
For more sophisticated algorithms, e.g. more than one while-loop is involved, you have to look into using threads(note it has its issues) or more recently I found a way of using python's Asyncio approach to do it. The learning curve for these two approaches is a lot steeper. To use the asyncio approach, you can explore modifying my answer to do what you want.
Best solution is to use threads however If you plan to do in simplest possible manner then implement the main loop inside your Tkinter GUI and once you read the packet simply update it on your GUI in same loop. Here is the Updated and working Code.
import tkinter as tk
import time
def setvalue(self, x):
self.label.config(text=x, )
root.update()
time.sleep(1)
def changevalues(self):
x = 99
self.label = tk.Label(root, text=x)
self.label.pack()
while x >0:
x -= 1
setvalue(root,x)
root = tk.Tk()
changevalues(root)
root.mainloop()
Here is a sample situation:
I have written a tk GUI that opens and edits e.g. .txt files. I'm currently on a mac, but I might also want to run this on windows. Currently the main Application class creates its own internal tk.Tk() like so
import tkinter as tk
class App:
def __init__(self):
self.root = tk.Tk()
tk.Label(master=self.root,text="content").pack()
if __name__ == '__main__':
a=App()
a.root.mainloop()
I want to add multi file capabilities to the application. In the current system this would result in 2 tk.Tk() instances running which I couldn't get to work and people say is unpredictable.
I want to be able to close any of the multiple files and still have the application running until the last window is closed and then the application quit or stays without a window (like on a mac).
My problem is that if I use tk.Toplevel() for each of my windows I have a ugly default tk.Tk() window which I would need to hide which seams inelegant. It would also be messy to determine which file is currently in focus for actions with the menubar.
I'm also considering each file being it's own application instance like on Windows, but that would fill the dock with repeated icons and be inconsistent with the rest of the system.
Here are the possible way I came up with:
Multiple Tks (Works but more complex examples are unpredictable):
import tkinter as tk
class App:
def __init__(self):
self.root = tk.Tk()
tk.Label(master=self.root,text="content").pack()
tk.Button(master=self.root,text="new_window",command=self.open).pack()
def open(self):
App()
if __name__ == '__main__':
a=App()
a.root.mainloop()
Hidden tk window and multiple Toplevels (Needs hiding the Tk and the osx menubar commands like view->change_colors would need to manaly decide where to direct comands):
import tkinter as tk
class App:
def __init__(self):
self.root = tk.Toplevel()
tk.Label(master=self.root,text="content").pack()
tk.Button(master=self.root,text="new_window",command=self.open).pack()
def open(self):
App()
if __name__ == '__main__':
tk.Tk()
a=App()
a.root.mainloop()
Each Window is its own Application and fully independant (Dosn't fit in with the other mac applications, the implementation is ugly):
#!/usr/local/bin/python3
import tkinter as tk
import sys,os
class App:
def __init__(self):
self.root = tk.Tk()
tk.Label(master=self.root,text="content").pack()
tk.Button(master=self.root,text="new_window",command=self.open).pack()
def open(self):
os.system(sys.argv[0]+"&")
if __name__ == '__main__':
a=App()
a.root.mainloop()
I was alos think of maybe doing it with threads but I think I must be over thinking it so I came here.
Is there a generally accepted way of doing this in tk?
The generally accepted way is just like you describe: use Toplevel for additional windows, and hide the root window if you don't want to see it.
Another choice is to make your main program a non-GYI app, and each window is created by spawning a new process.
Hidden tk window and multyple Toplevels (Needs hiding the Tk and the osx menubar commands like view->change_colors would need to manaly decide where to direct comands):
It is easy to get into "which goes with what" hell with multiple Toplevels. Instead create a separate class for each new Toplevel with built in responses to button clicks, etc. Pass "root" to the class and let it create, update, and destroy as necessary, and keep track of it's own variables. If you want to use variables elsewhere, then store the class instance when the Toplevel classes are called and create the variables within the class as instance attributes.
I'm trying to learn how to use the thread module. I followed along with the instructions here: http://effbot.org/zone/tkinter-threads.htm
My hope is the test script will:
Print out the "count" every two seconds
Show a pop-up dialog window (also every 2 seconds)
The pop-ups should be allowed to accumulate (if I don't click "OK" for a while, there should be
multiple pop-ups)
However, when I run this script it will freeze the main window and after a while crash. I think I'm not implementing the thread module correctly.
Could someone please have a look and point out what I'm doing wrong?
Here is what I've tried so far:
from Tkinter import *
import thread
import Queue
import time
class TestApp:
def __init__(self, parent):
self.super_Parent = parent
self.main_container = Frame(parent)
self.main_container.pack()
self.top_frame = Frame(self.main_container)
self.top_frame.pack(side=TOP)
self.bottom_frame = Frame(self.main_container)
self.bottom_frame.pack(side=TOP)
self.text_box = Text(self.top_frame)
self.text_box.config(height=20, width=20)
self.text_box.pack()
self.queue = Queue.Queue()
self.update_me()
def show_popup(self):
self.my_popup = Toplevel(self.main_container)
self.my_popup.geometry('100x100')
self.popup_label = Label(self.my_popup, text="Hello!")
self.popup_label.pack(side=TOP)
self.pop_button = Button(self.my_popup, text="OK", command=self.my_popup.destroy)
self.pop_button.pack(side=TOP)
def write(self, line):
self.queue.put(line)
def update_me(self):
try:
while 1:
line = self.queue.get_nowait()
if line is None:
self.text_box.delete(1.0, END)
else:
self.text_box.insert(END, str(line))
self.text_box.see(END)
self.text_box.update_idletasks()
except Queue.Empty:
pass
self.text_box.after(100, self.update_me)
def pipeToWidget(input, widget):
widget.write(input)
def start_thread():
thread.start_new(start_test, (widget,))
def start_test(widget):
count = 0
while True:
pipeToWidget(str(count) + "\n", widget)
count += 1
time.sleep(2)
widget.show_popup()
root = Tk()
widget = TestApp(root)
start_button = Button(widget.bottom_frame, command=start_thread)
start_button.configure(text="Start Test")
start_button.pack(side=LEFT)
root.title("Testing Thread Module")
root.mainloop()
I can't reproduce your problem, but I can see why it would happen.
You're using the queue to pass messages from the background thread to the main thread for updating text_box, which is correct. But you're also calling widget.show_popup() from the background thread, which means it creates and displays a new Toplevel in the background thread. That's not correct.
All UI code must run in the same thread—not all UI code for each top-level window, all UI code period. On some platforms, you may get away with running each window in its own thread (or even free-threading everything), but that isn't supposed to work, and definitely will crash or do improper things on some platforms. (Also, that single UI thread has to be the initial thread on some platforms, but that isn't relevant here.)
So, to fix this, you need to do the same dance for creating the popups that you do for updating the textbox.
The obvious way to do that is to move the widget.show_popup() to the loop in update_me(). If you want it to happen 2 seconds after the textbox updates, just add self.top_frame.after(2000, self.show_popup) to the method.
But I'm guessing you're trying to teach yourself how to have multiple independent updating mechanisms, so telling you "just use a single update queue for everything" may not be a good answer. In that case, just create two queues, and a separate update method servicing each queue. Then, do your pipeToWidget, sleep 2 seconds, then pipeToPopup.
Another way around this is to use mtTkinter. It basically does exactly what you're doing, but makes it automatic, pushing each actual Tk GUI call onto a queue to be run later by the main loop. Of course your objects themselves have to be thread-safe, and this also means that you have to deal with the GUI calls from one thread getting interleaved with calls from another thread. But as long as neither of those is a problem (and they don't seem to be in your case), it's like magic.
If you want to know why this is freezing and/or crashing for you on Win7 and not for me on OS X 10.8… well, you really need to look into a mess of Tcl, C, and Python code, and also at how each thing is built. And, unless it's something simple (like your Tk build isn't free-threaded), it wouldn't tell you much anyway. The code isn't supposed to work, and if it seems to work for me… that probably just means it would work every time until the most important demo of my career, at which point it would fail.
I have a relatively large application written in Python and using PyQT as a GUI frontend. The entire application is in one class, in one file.
Here's an example code:
class Application(QMainWindow):
def __init__(self):
super(etc...)
self.connect(self.mainBtn, SIGNAL("clicked()"), self.do_stuff)
def do_stuff(self):
<checking some parameters>
else:
do_some_other_long_stuff()
def do_some_other_long_stuff(self):
500 lines of code of stuff doing
However, this is the problem: when I click the mainBtn, everything goes fine, except the GUI kind of freezes - I can't do anything else until the function is performed (and it's a web scraper so it takes quite a bit of time). When the function do_some_other_long_stuff ends, everything goes back to normal. This is really irritating.
Is there a way to somehow "background" the do_some_other_stuff process? I looked into QThreads and it seems it does just that, however that would require me to rewrite basically all of code, put half of my program in a different class, and therefore have to change all the variable names (when getting a variable from GUI class and putting it in working class)
Duplicate of Handling gui with different threads,
How to keep track of thread progress in Python without freezing the PyQt GUI?, etc.
Your do_stuff() function needs to start up the computing thread and then return. Multi-threading is the name given to running multiple activities in a single process - by definition if something is going on "in the background", it's running on a separate thread. But you don't need to split functions into a different classes to use threads, just be sure that the computing functions don't do anything with the GUI and the main thread doesn't call any of the functions used by the computing thread.
EDIT 10/23: Here's a silly example of running threads in a single class - nothing in the language or the threading library requires a different class for each thread. The examples probably use a separate class for processing to illustrate good modular programming.
from tkinter import *
import threading
class MyApp:
def __init__(self, root):
self.root = root
self.timer_evt = threading.Event()
cf = Frame(root, borderwidth=1, relief="raised")
cf.pack()
Button(cf, text="Run", command=self.Run).pack(fill=X)
Button(cf, text="Pause", command=self.Pause).pack(fill=X)
Button(cf, text="Kill", command=self.Kill).pack(fill=X)
def process_stuff(self): # processing threads
while self.go:
print("Spam... ")
self.timer_evt.wait()
self.timer_evt.clear()
def Run(self): # start another thread
self.go = 1
threading.Thread(target=self.process_stuff, name="_proc").start()
self.root.after(0, self.tick)
def Pause(self):
self.go = 0
def Kill(self): # wake threads up so they can die
self.go = 0
self.timer_evt.set()
def tick(self):
if self.go:
self.timer_evt.set() # unblock processing threads
self.root.after(1000, self.tick)
def main():
root = Tk()
root.title("ProcessingThread")
app = MyApp(root)
root.mainloop()
main()