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.
Related
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.
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)
MyButton1 =Button(master, text='Quit',bg="grey",width=20,
command=master.quit)
MyButton1.place(x=200, y=100)
MyButton2 =Button(master, text='Propagate', bg="grey",width=20,
command=mainmethod)
MyButton2.place(x=1000, y=100)
master.geometry("1500x1500")
master.mainloop( )
In the above code after pressing propagate button mainmethod is invoking..
I wrote my logic in main method where this method alone taking 2minutes to execute in the mean time GUI going unresponsive state for few min and later displaying all my required output on text box i inserted
whether any away to avoid the unresponsive issue apart from using multi threading
and i am looking such that after pressing propagate button button should disabled and window should not go unresponsive and display text.insert statements continuously which i added in main method ?????
To prevent hanging, you need to separate the calculations in the mainmethod from Tkinter's main loop by executing them in different threads. However, threading system in Python is not that well-developed as in other languages (AFAIK) because of GIL (Global Interpreter Lock), but there is an alternative - using processes just like threads. This is possible with multiprocessing library.
In order to just prevent hanging, you could create another function
from multiprocessing import Process
def mainmethodLaunch():
global mainmethodProcess
mainmethodProcess = Process(target=mainmethod)
mainmethodProcess.start()
And bind this function to MyButton2 instead of mainmethod itself.
Docs: https://docs.python.org/2/library/multiprocessing.html#the-process-class
You can see p.join in the example. join method will cause your main process to wait for the other one to complete, which you don't want.
So when you press the button, mainmethodLaunch function will be invoked, and it will create another process executing mainmethod. mainmethodLaunch function's own run duration should be insignificant. Due to usage of another process, Tkinter window will not hang. However, if you do just this, you will not be able to interact with mainmethod process in any kind while it will be working.
In order to let these processes communicate with each other, you could use pipes (https://docs.python.org/2/library/multiprocessing.html#exchanging-objects-between-processes)
I guess the example is quite clear.
In order to receive some data from the mainmethod process over time, you will have to poll the parent_conn once a little time, let's say, second. This can be achieved with Tkinter's after method
(tkinter: how to use after method)
IMPORTANT NOTE: when using multiprocessing, you MUST initialize the program in if __name__ == '__main__': block. I mean, there should be no doing-something code outside functions and this block, no doing-something code with zero indent.
This is because multiprocessing is going to fork the same Python executable file, and it will have to distinguish the main process from the forked one, and not do initializing stuff in the forked one.
Check twice if you have done that because if you make such a mistake, it can cost you hanging of not just Tkinter window, but the whole system :)
Because the process will be going to fork itself endlessly, consuming all RAM you have, regardless of how much you have.
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.
I want to spawn another process to display an error message asynchronously while the rest of the application continues.
I'm using the multiprocessing module in Python 2.6 to create the process and I'm trying to display the window with TKinter.
This code worked okay on Windows, but running it on Linux the TKinter window does not appear if I call 'showerror("MyApp Error", "Something bad happened.")'. It does appear if I run it in the same process by calling showerrorprocess directly. Given this, it seems TKinter is working properly. I can print to the console and do other things from processes spawned by multiprocessing, so it seems to be working too.
They just don't seem to work together. Do I need to do something special to allow spawned subprocesses to create windows?
from multiprocessing import Process
from Tkinter import Tk, Text, END, BOTH, DISABLED
import sys
import traceback
def showerrorprocess(title,text):
"""Pop up a window with the given title and text. The
text will be selectable (so you can copy it to the
clipboard) but not editable. Returns when the
window is closed."""
root = Tk()
root.title(title)
text_box = Text(root,width=80,height=15)
text_box.pack(fill=BOTH)
text_box.insert(END,text)
text_box.config(state=DISABLED)
def quit():
root.destroy()
root.quit()
root.protocol("WM_DELETE_WINDOW", quit)
root.mainloop()
def showerror(title,text):
"""Pop up a window with the given title and text. The
text will be selectable (so you can copy it to the
clipboard) but not editable. Runs asynchronously in
a new child process."""
process = Process(target=showerrorprocess,args=(title,text))
process.start()
Edit
The issue seems to be that TKinter was imported by the parent process, and "inherited" into the child process, but somehow its state is inextricably linked to the parent process and it cannot work in the child. So long as you make sure not to import TKinter before you spawn the child process, it will work because then it is the child process that is importing it for the first time.
This discussion could be helpful.
Here's some sample problems I found:
While the multiprocessing module follows threading closely, it's definitely not an exact match. One example: since parameters to a
process must be pickleable, I had to go through a lot of code
changes to avoid passing Tkinter objects since these aren't
pickleable. This doesn't occur with the threading module.
process.terminate() doesn't really work after the first attempt. The second or third attempt simply hangs the interpreter, probably
because data structures are corrupted (mentioned in the API, but this
is little consolation).
Maybe calling the shell command xhost + before calling your program from that same shell will work?
I am guessing your problem lies with the X-server.