How to easily avoid Tkinter freezing? - python

I developed a simple Python application doing some stuff, then I decided to add a simple GUI using Tkinter.
The problem is that, while the main function is doing its stuff, the window freezes.
I know it's a common problem and I've already read that I should use multithreads (very complicated, because the function updates the GUI too) or divide my code in different function, each one working for a little time.
Anyway I don't want to change my code for such a stupid application.
My question is: is it possible there's no easy way to update my Tkinter window every second? I just want to apply the KISS rule!
I'll give you a pseudo code example below that I tried but didn't work:
class Gui:
[...]#costructor and other stuff
def refresh(self):
self.root.update()
self.root.after(1000,self.refresh)
def start(self):
self.refresh()
doingALotOfStuff()
#outside
GUI = Gui(Tk())
GUI.mainloop()
It simply will execute refresh only once, and I cannot understand why.
Thanks a lot for your help.

Tkinter is in a mainloop. Which basically means it's constantly refreshing the window, waiting for buttons to be clicked, words to be typed, running callbacks, etc. When you run some code on the same thread that mainloop is on, then nothing else is going to perform on the mainloop until that section of code is done. A very simple workaround is spawning a long running process onto a separate thread. This will still be able to communicate with Tkinter and update it's GUI (for the most part).
Here's a simple example that doesn't drastically modify your psuedo code:
import threading
class Gui:
[...]#costructor and other stuff
def refresh(self):
self.root.update()
self.root.after(1000,self.refresh)
def start(self):
self.refresh()
threading.Thread(target=doingALotOfStuff).start()
#outside
GUI = Gui(Tk())
GUI.mainloop()
This answer goes into some detail on mainloop and how it blocks your code.
Here's another approach that goes over starting the GUI on it's own thread and then running different code after.

'Not responding' problem can be avoided using Multithreading in python using the thread module.
If you've defined any function, say combine() due to which the Tkinter window is freezing, then make another function to start combine() in background as shown below:
import threading
def combine():
...
def start_combine_in_bg():
threading.Thread(target=combine).start()
Now instead of calling combine(), you have to call start_combine_in_bg()
I did this and now my window is not freezing so I shared it here. Remember you can run only one thread at a time.
Have a look for reference: stackoverflow - avoid freezing of tkinter window using one line multithreading code

Here's how I managed to work around this issue (maybe it's quite dirty I don't know):
Whenever I update my tkinter window through the doingALotOfStuff() function, I call myTk.update().
It avoids freezing for me.

Related

How do I Update Tkinter constantly without freezing everything else?

I need to send updates to tkinter from another thread, which should be handled immediately. Sadly Tkinter freezes whenever I try multithreading.
My Attempt:
I've read through several Tkinter-threaded pages but didn't find anything that works well, since most of them try creating a new thread on button.click(), which doesn't help here.
I tried not calling .mainloop() and instead calling the update functions myself, whenever an update comes in:
#GUI
def update(self, string):
self._interactor.config(text=string)
#interactor is a button
self._tk.update_idletasks()
self._tk.update()
This works fine until I use a loop with sleep() in the master to constantly update the text. GUI and master are frozen during sleep().
So I tried to use a threaded timer as discussed here.
#MASTER
gui=gui_remote(self)
def changetext():
text=self.gettextsomewhere()
self._gui.update(text)
loop=RepeatedTimer(5, changetext)
But this just leads to the following error, thrown by Tkinter:
RuntimeError: main thread is not in main loop
Having a hard time on how to solve this. Is it possible to call a GUI class on main thread and still have proper access to its functions?
How I got to this point:
For my project, I need a button, which represents several Buttons.
Every y (eg. 1.5) seconds, the displayed text should be updated to a new one from the outside.
Also, I want to keep GUI, Controller and Data separated, (using blueprint methods) so that later adjustments on each of them will be easier.
I already got it to work, using TK's .after() function, but I had to use GUI and controlling functions closely together.
My Plan
Have a GUI class, which is updateable from another object via simple public functions. The other object (the master) should be able to create a GUI object and call the GUI's update functions with new data every y seconds.
When the GUI button is clicked, simply call a certain method at master every time:
#GUI example
from tkinter import Tk, Button, Frame, Label
class gui_sample:
def __init__(self, master):
"""This is the very simple GUI"""
self._master=master
self._tk=Tk()
self._interactor= Button(self._tk, text="Apfelsaft", command=self._click)
self._interactor.pack()
self._tk.mainloop()
def update(self, string):
"""Handle interactor update"""
self._interactor.config(text=string)
def _click(self):
self._master.click()
#MASTER
from gui_module import *
class Controller:
def __init__(self):
self._gui=gui_sample(self)
self._run()
def _run(self):
#call this every 5 seconds
new_text=self.gettextfromsomewhere()
self._gui.update(new_text)
def click():
#do something
pass
#this code is just a blueprint it probably does nothing
My Problem:
I don't want the master to use TK functions since I might switch to another UI module later and keep the master's functionality. The master will constantly loop through what's being displayed next and needs to be accessible at the same time. Using loops with sleep() isn't a good idea since they will block both, the master and the GUI. Calling .mainloop() is also problematic since it will block all other programs. The gui should always respond to updates and not ask for them.

appJar status bar doesn't update in real time

I've set up a simple appJar UI, which has an "execute" button that calls a function containing code that takes a minute to run. I have injected my gui() variable, app, into this function.
There are 4 major steps, after each of which I would like a Statusbar to update to reflect that a step has been completed. However, what tends to happen is that as the function code runs, the GUI becomes unresponsive and it isn't until the code completes execution that ALL of the changes to the status bar are displayed at once.
My question is how should I be handling the UI such that the Statusbar is updated in real time?
appJar is just a wrapper around python tkinter module from standard library.
While your code is running, the ui is not running, thus it becomes unresponsive. If you want the ui to remain responsible, you have to return control from your code to the ui library from time to time.
That can be done by calling gui.topLevel.update() in your code, or by using asynchronous programming and having the main async loop call it, or by using threads.
Which one of those is the best, depends on what your program is doing.
appJar also has built in support for threads: http://appjar.info/pythonThreads/
You can call the function that takes a long time in a thread: app.thread(myFunction, param1, param2)
And, if you want to get the thread to update the GUI, you'll need to use the update queue: app.queueFunction(app.setMeter, "myMeter", 50)

Tkinter threading freezes after executing twice

I have recently started using multithreading to be able to, for example, supply input during a for loop. This was successfull but I wanted to try more.
When I tried threading in combination with Tkinter things went wrong.
So for my example I have a very simple piece code (Python 2.7.11). I realize it beats the point of threading but it shows my problem:
import Tkinter
import threading
def func():
root=Tkinter.Tk() #Open Tkinter window
Tkinter.mainloop()
root.quit() #Extra quit, although it doesn't seem to help
thread1=threading.Thread(target=func)
thread1.start()
thread1.join() #Wait for thread to end (for me to close the window)
del thread1 #Extra deletes (although they don't seem to help)
del func
Running this code once works fine. The Tkinter window opens, I close it manually and the rest works fine. However if I run it again the interpreter freezes. If a second thread, or a simple loose print statement, is added it doesn't even start those. The entire enterpreter freezes and I can only re-run this code by restarting the Kernel.
So do I have to quit the thread/Tkinter in a special way to prevent this from happening?
Additional information:
My CPU usage goes way up. When running it the 2nd time task manager shows up to 30% CPU usage. So I guess there is an infinite loop going on somewhere.
I have read that Tkinter does not take threading well, but I have seen instances where this workes. What I have here is a very simple thing so it must be something I am doing wrong.
Hopefully someone can help me, because I couldn't find an answer in whatever way I tried to Google it.
Thanks in advance.

Closing threaded Tkinter window

I use a Tkinter window to visualize some output of my programm. The window is threaded (see basic structure below) and basically it works quite fine. So far, I only have trouble closing the window. When I clicke the "X" button for closing the window it works.
However, when I call the Monitor.close() method from the main programm that starts the monitor thread, the window just freezes (e.g., it doesn't react on clicking the "X" button) and the thread monitor keeps running. Thus, the main program does not exit.
So, at the moment, I also have to close first the window "manually" by clicking the closing button and then the main program. Not a big issue, but it would be great, if the main program could close the window by itself. Any hints?
Thanks and best regards,
Christian
class Monitor(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.start()
def close(self):
self.root.quit()
self.root.destroy()
def run(self):
self.root=Tkinter.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.close)
self.root.mainloop()
Python Threading and Tk(inter) used in this way do not mix well, as they violate the Tcl/Tk threading model of using Tk just from one thread.
It works great with message passing though, just not with direct calls from a thread. So you need to add some message passing via Queue to this.
Have a look at http://effbot.org/zone/tkinter-threads.htm for an example.

Threaded Tkinter script crashes when creating the second Toplevel widget

I have a Python script which uses Tkinter for the GUI. My little script should create a Toplevel widget every X seconds. When I run my code, the first Toplevel widget is created successfully, but when it tries to create a second one the program crashes.
What I am doing is using the after method to call the function startCounting every 5 seconds alongside root's mainloop. Every time this function is called, I append a Toplevel widget object into a list and start a new thread which hopefully will be running the new mainloop.
I would be very grateful if someone could figure this problem out. By the way, this is just a little script that I am currently using to solve my problem, which is preventing me from going on with my real school project.
The code:
import threading,thread
from Tkinter import *
def startCounting():
global root
global topLevelList
global classInstance
topLevelList.append (Toplevel())
topLevelList[len(topLevelList)-1].title("Child")
classInstance.append(mainLoopThread(topLevelList[len(topLevelList)-1]))
root.after(5000,startCounting)
class mainLoopThread(threading.Thread):
def __init__(self,toplevelW):
self.toplevelW = toplevelW
threading.Thread.__init__(self)
self.start()
def run(self):
self.toplevelW.mainloop()
global classInstance
classInstance = []
global topLevelList
topLevelList = []
global root
root = Tk()
root.title("Main")
startCounting()
root.mainloop()
Tkinter is designed to run from the main thread, only. See the docs:
Just run all UI code in the main
thread, and let the writers write to a
Queue object; e.g.
...and a substantial example follows, showing secondary threads writing requests to a queue, and the main loop being exclusively responsible for all direct interactions with Tk.
Many objects and subsystems don't like receiving requests from multiple various threads, and in the case of GUI toolkit it's not rare to need specfically to use the main thread only.
The right Python architecture for this issue is always to devote a thread (the main one, if one must) to serving the finicky object or subsystem; every other thread requiring interaction with said subsystem or object must them obtain it by queueing requests to the dedicated thread (and possibly waiting on a "return queue" for results, if results are required as a consequence of some request). This is also a very sound Python architecture for general-purpose threading (and I expound on it at length in "Python in a Nutshell", but that's another subject;-).
Tkinter has issues dealing with input from multiple threads, I use mtTkinter instead, you won't need to change any code and everything will work fine. Just import mtTkinter instead of Tkinter.
You can get it here:
http://tkinter.unpythonic.net/wiki/mtTkinter
Is there a reason you want (or think you need) one event loop per toplevel window? A single event loop is able to handle dozens (if not hundreds or thousands) of toplevel windows. And, as has been pointed out in another answer, you can't run this event loop in a separate thread.
So, to fix your code you need to only use a single event loop, and have that run in the main thread.

Categories