For few days now, I have been researching on how to fix this issue. Basically I have a console application where in certain stages, I call pysimplegui to create a notification window or:
main console program that needs to always work on the background
if capture I.e a keystroke, create an alert window. On this stage, I need the main console program to still be capturing keystroke while a pysimplegui window is created. Hence why I am using a thread to open the pysimplegui window in a new thread.
How I have developed my program.
if (threading.active_count() < 2):
wt = threading.Thread(target=createwindow, name="noty", args=(argumnets,),
daemon=True)
wt.setDaemon(True) # just to be safe
wt.start()
wt.join()
create window:
def createalertwindow(Attack):
# I have removed this part of the code where I design the gui window just to make easy to understand
e, v = win.read(timeout=5000)
if (e == "e"):
print("e button clicked")
elif (e == "Ok"):
win.close()
# close first window
win.close()
Now Every time I run above code I get below exception errors:
Exception ignored in: <function Variable.del at 0x000001EACB37CCA0>
Traceback (most recent call last):
File "C:\Users\Abdul\AppData\Local\Programs\Python\Python39\lib\tkinter_init_.py", line 350, in del
if self._tk.getboolean(self._tk.call("info", "exists", self.name)):
RuntimeError: main thread is not in main loop
Exception ignored in: <function Variable.del at 0x000001EACB37CCA0>
Traceback (most recent call last):
File "C:\Users\Abdul\AppData\Local\Programs\Python\Python39\lib\tkinter_init.py", line 350, in del
if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
RuntimeError: main thread is not in main loop
I read many question already out there, but found none that could fix the issue for me I.e I tried using:
plt.switch_backend('agg')
wt = threading.Thread(target=createwindow, name="noty", args=(argumnets,),
daemon=True)
wt.setDaemon(True)
...
I may be note worthy to mention again that my main program is a console application not a gui application.
I am using threading library for threading but am okay to switch if I have better options and am using pysimplegui for create gui windows.
It looks like you cannot call PySimpleGUI/tkinter in another thread.
Here, try to set main program as one thread and called in PySimpleGUI/tkinter.
The same, remember that don't call PySimpleGUI directly in your main_program and use method window.write_event_value to generate an event, then do it in your event loop.
Example code,
from time import sleep
import threading
import PySimpleGUI as sg
def hello():
layout = [[sg.Text("Hello, my friend !")]]
window = sg.Window("Hello", layout, keep_on_top=True, modal=True)
window.read(timeout=1000, close=True)
def main_program():
count = 5
while count > 0:
window.write_event_value("Hello", None)
sleep(3)
count -=1
window.write_event_value(sg.WINDOW_CLOSED, None)
layout = [[]]
window = sg.Window("Title", layout, alpha_channel=0, finalize=True)
threading.Thread(target=main_program, daemon=True).start()
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
elif event == "Hello":
hello()
window.close()
Related
I'm currently working with Tkinter to create a GUI for my Script. Among other things there is a function which writes and save some Data in some Files. To visualize the progress for the User i got an label which shows the progress. When i press the button to execute the function
button_download_data = tk.Button(text="Get Data",command=gatherData)
the window freezes and beside the counter for the progress increasaes it isnt shown due to the window is frozen. My solution was to start the function in a new thread using the threading modul.
button_download_data = tk.Button(text="Get Data",command=threading.Thread(target=gatherData).start)
Now the progress is showed but i cant press the button again because i get an error:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\WPy64-31050\python-3.10.5.amd64\lib\tkinter\__init__.py", line 1921, in __call__
return self.func(*args)
File "C:\WPy64-31050\python-3.10.5.amd64\lib\threading.py", line 930, in start
raise RuntimeError("threads can only be started once")
RuntimeError: threads can only be started once
I tried to "kill" the thread when the function is done with raise Exception() and sys.Exit() but it doesn't work at all.
I figuerd out that i can outsource the thread start out of the tkinter button line with:
def gatherDate():
do something
def threadStart():
threading.Thread(target=gatherData).start
button_download_data = tk.Button(text="Get Data",command=threadStart)
and i think it might help to start a new thread on button press and not the same again but i cant imagine how.
You should be able to handle this by creating a separate function to spawn new worker threads as needed - here's a very basic example
import tkinter as tk
from threading import Thread
from time import sleep # for example - simulate a long-running process
def get_data():
print('foo') # do whatever you need to do here
sleep(2.0) # simulate the thread 'working' on something...for example
def spawn_thread():
t = Thread(target=get_data, daemon=True)
t.start()
root = tk.Tk()
button_download_data = tk.Button(root, text='Get Data', command=spawn_thread)
button_download_data.pack()
if __name__ == '__main__':
root.mainloop()
You could simplify spawn_thread a little by skipping the variable assignment t=... and doing Thread(target=get_data, daemon=True).start() instead (as long as you don't need access to the Thread object t for anything else)
Ctrl-C/SIGTERM/SIGINT seem to be ignored by tkinter. Normally it can be captured again with a callback. This doesn't seem to be working, so I thought I'd run tkinter in another thread since its mainloop() is an infinite loop and blocks. I actually also want to do this to read from stdin in a separate thread. Even after this, Ctrl-C is still not processed until I close the window. Here's my MWE:
#! /usr/bin/env python
import Tkinter as tk
import threading
import signal
import sys
class MyTkApp(threading.Thread):
def run(self):
self.root = tk.Tk()
self.root.mainloop()
app = MyTkApp()
app.start()
def signal_handler(signal, frame):
sys.stderr.write("Exiting...\n")
# think only one of these is needed, not sure
app.root.destroy()
app.root.quit()
signal.signal(signal.SIGINT, signal_handler)
Results:
Run the app
Ctrl-C in the terminal (nothing happens)
Close the window
"Exiting..." is printed and I get an error about the loop already having exited.
What's going on here and how can I make Ctrl-C from the terminal close the app?
Update: Adding a poll, as suggested, works in the main thread but does not help when started in another thread...
class MyTkApp(threading.Thread):
def poll(self):
sys.stderr.write("poll\n")
self.root.after(50, self.poll)
def run(self):
self.root = tk.Tk()
self.root.after(50, self.poll)
self.root.mainloop()
here's a working example that catches control c in the windows or from the command line. this was tested with 3.7.2 this seems simpler than the other solutions. I almost feel like I'm missing something.
import tkinter as TK
import signal
def hello():
print("Hello")
root = TK.Tk()
TK.Button(root, text="Hi", command=(hello)).pack( )
def handler(event):
root.destroy()
print('caught ^C')
def check():
root.after(500, check) # time in ms.
# the or is a hack just because I've shoving all this in a lambda. setup before calling main loop
signal.signal(signal.SIGINT, lambda x,y : print('terminal ^C') or handler(None))
# this let's the terminal ^C get sampled every so often
root.after(500, check) # time in ms.
root.bind_all('<Control-c>', handler)
root.mainloop()
Since your tkinter app is running in another thread, you do not need to set up the signal handler in the main thread and just use the following code block after the app.start() statement:
import time
while app.is_alive():
try:
time.sleep(0.5)
except KeyboardInterrupt:
app.root.destroy()
break
You can then use Ctrl-C to raise the KeyboardInterrupt exception to close the tkinter app and break the while loop. The while loop will also be terminated if you close your tkinter app.
Note that the above code is working only in Python 2 (as you use Tkinter in your code).
Proper CTRL-C & SIGINT Usage in Python
The problem is that you are exiting the main thread, so the signal handler is basically useless. You need to keep it running, in a while loop, or my personal preference, Events from threading module. You can also just catch the KeyboardInterrupt exception generated by the CTRL-C event, rather than dealing with signal handlers.
SIGINT in Tkinter
Using tkinter, you must have the tkinter app run in a separate thread, so that it doesn't interfere with the signal handler or KeyboardInterrupt exception. In the handler, to exit, you need to destroy then update tkinter root. Update allows the tkinter to update so that it closes, without waiting for mainloop. Otherwise, user has to click on the active window to activate mainloop.
# Python 3
from tkinter import *
from threading import Thread
import signal
class MyTkApp(Thread):
def run(self):
self.root = Tk()
self.root.mainloop()
def sigint_handler(sig, frame):
app.root.quit()
app.root.update()
app = MyTkApp()
# Set signal before starting
signal.signal(signal.SIGINT, sigint_handler)
app.start()
Note: SIGINTs can also be caught if you set handler in same thread as tkinter mainloop, but you need to make tkinter window active after the signal so that it's mainloop can run. There is no way around this unless you run in new thread.
More Information on Tkinter & Command Line Communication
For more on communicating between tkinter and the command line, see Using Tkinter Without Mainloop. Basically, you can use update method in your loop, and then communicate with other threads and processes, etc. I would personally NOT recommend this, as you are essentially doing the job of the python thread control system, which is probably opposite of what you want to do. (python has a process that runs all internal threads in one external thread, so you are not taking advantage of multitheading, unless using multiprocessing module)
# Python 2
from Tkinter import *
ROOT = Tk()
LABEL = Label(ROOT, text="Hello, world!")
LABEL.pack()
LOOP_ACTIVE = True
while LOOP_ACTIVE:
ROOT.update()
USER_INPUT = raw_input("Give me your command! Just type \"exit\" to close: ")
if USER_INPUT == "exit":
ROOT.quit()
LOOP_ACTIVE = False
else:
LABEL = Label(ROOT, text=USER_INPUT)
LABEL.pack()
I have a python program which is scraping web data for a client. tkinter is used for the interface. Outline is:
Window 1 lets the user select what information to scrape.
Window 1 closes
Separate thread is started for the scraper. This thread will in turn spawn more threads to allow multiple pages to be downloaded at once.
Window 2 opens to show download progress (e.g. "downloading client 5 of 17")
User closes Window 2 to end program.
The program will work for the first few hundred pages, but then it starts spitting out the error message:
Traceback (most recent call last):
File "C:\Users\Me\AppData\Local\Programs\Python\Python35-32\lib\tkinter\__init__.py", line 248, in __del__
if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
RuntimeError: main thread is not in main loop
Exception ignored in: <bound method Variable.__del__ of <tkinter.IntVar object at 0x03245510>>
multiple times until all the threads have been stopped. No idea what could be causing this error. The actual code is:
import scraper, threading
import tkinter as tk
from queue import Queue
outputQueue = Queue()
class OutputRedirect(object):
def __init__():
super().__init__()
def write(self, string):
outputQueue.put(string)
def getInformation():
stdout = sys.stdout
sys.stdout = OutputRedirect()
scraper.startThreads()
scraper.startPulling()
sys.stdout = stdout
def updateTextField(window, root):
if not outputQueue.empty():
string = outputQueue.get()
window.textArea.insert("insert", string)
outputQueue.task_done()
root.after(1, updateTextField, window, root)
'''widget/window definitions - not important'''
guiInfo = {"stuff1": [], "stuff2": []}
root = tk.Tk()
window1 = Window1(root, guiInfo)
window1.mainloop()
pullThread = threading.Thread(target=pullClaims,
args=(list(guiInfo["stuff1"]),
list(guiInfo["stuff2"])), daemon=True)
pullThread.start()
root = tk.Tk()
window2 = Window2(root)
root.after(0, updateTextField, window2, root)
window2.mainloop()
The scraper program (which works fine on its own) uses print statements for user feedback. Rather than re-write everything, I just pointed stdout to a queue. The main thread uses the "after" function to check on the queue a few times a second. If there is anything in it then it gets printed to the Text widget on the window.
I've put try/catch just about everywhere in the code, but they haven't caught a thing. I'm convinced the problem is in the mainloop itself, but I can't find any up to date information for how to stick something new in it. Any help would be greatly appreciated.
To handle tkinter errors you do the following
class TkErrorCatcher:
'''
In some cases tkinter will only print the traceback.
Enables the program to catch tkinter errors normally
To use
import tkinter
tkinter.CallWrapper = TkErrorCatcher
'''
def __init__(self, func, subst, widget):
self.func = func
self.subst = subst
self.widget = widget
def __call__(self, *args):
try:
if self.subst:
args = self.subst(*args)
return self.func(*args)
except SystemExit as msg:
raise SystemExit(msg)
except Exception as err:
raise err
import tkinter
tkinter.CallWrapper = TkErrorCatcher
But in your case please do not do this. This should only ever be done in the case of wanting to hide error messages from your users in production time. As commented above you have a nono's going on.
To spawn multiple windows you can use tkinter.Toplevel
I would recommend in general to read
http://www.tkdocs.com/tutorial/index.html
http://effbot.org/tkinterbook/tkinter-hello-tkinter.htm
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html
and for your specific problem of threading in tkinter this blog post nails it. Basically you need to have the tkinter mainloop blocking the programs main thread, then use after calls from other threads to run other code in the mainloop.
http://stupidpythonideas.blogspot.de/2013/10/why-your-gui-app-freezes.html
I have a script which has a record and stop button, the record button does an infinite loop, but it also blocks the other button (stop button). All I wanted to build is a process which starts at click of record button and stops are click of stop button. Here is the script:
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# This file is in the public domain
### END LICENSE
from locale import gettext as _
from gi.repository import Gtk # pylint: disable=E0611
import logging
logger = logging.getLogger('recordme')
from recordme_lib import Window
from recordme.AboutRecordmeDialog import AboutRecordmeDialog
from recordme.PreferencesRecordmeDialog import PreferencesRecordmeDialog
class RecordmeWindow(Window):
__gtype_name__ = "RecordmeWindow"
record = False
def finish_initializing(self, builder): # pylint: disable=E1002
"""Set up the main window"""
super(RecordmeWindow, self).finish_initializing(builder)
self.AboutDialog = AboutRecordmeDialog
self.PreferencesDialog = PreferencesRecordmeDialog
# Code for other initialization actions should be added here.
self.button1 = self.builder.get_object('button1')
self.button2 = self.builder.get_object('button2')
def on_button1_clicked(self, widget):
while(not self.record):
print 'button1 clicked'
while gtk.events_pending():
gtk.main_iteration(False)
Any ideas about this problem ?
I encountered similar programs in WX, which is also event based. The best (and possibly only) way I found to solve the problem is to create a function that runs on a timer during the main loop. Mine ran periodically, but you could also just set it to wait and close the loop when you run your function. In GTK, you have to do this with another module, "gobject". Here is an example of a method that runs periodically in GTK.
import gobject
class gtk_object(object):
def __init__(self):
gobject.timeout_add(100, self.my_function)
def my_function(self):
#do something here, like stopping the loop or having a timer to stop the loop
return True
Assuming your record functionality is cpu-intensive and/or may block and/or needs soft realtime assurance, I would recommend moving it off to a separate "worker" thread.
Then, create a window and your buttons.
Here, when "record" is clicked, I signal the worker to start recording; when "stop" is clicked, signal worker to stop; Optionally, when stop is clicked, terminate the main loop if you want your app to exit.
Additional control logic to terminate the app when window is closed and terminate the worker thread correctly is at the very bottom.
#!/usr/bin/env python
import time
import logging
import threading
from gi.repository import Gtk
class Worker(threading.Thread):
should_record = False
quit = False
def run(self):
while not self.quit:
if self.should_record:
logging.warn("recording...")
# cpu-intensive code here
else:
time.sleep(0.1)
class MainWindow(Gtk.Window):
def __init__(self):
super(MainWindow, self).__init__()
self.worker = Worker()
self.worker.start()
hb = Gtk.Box()
self.add(hb)
record = Gtk.Button("Record")
stop = Gtk.Button("Stop")
hb.add(record)
hb.add(stop)
def command(arg):
self.worker.should_record = arg
record.connect("clicked", lambda _b: command(True))
stop.connect("clicked", lambda _b: command(False))
# optional, if you want to quit the app on stop as well
stop.connect("clicked", lambda _b: Gtk.main_quit())
if __name__ == "__main__":
main = MainWindow()
try:
# optional, if you want to support close window to quit app
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
finally:
main.worker.quit = True
main.worker.join()
old stuff
Ideally you wan to use Gtk.main() instead of Gtk.main_iteration() in Gtk+ 3.
In Gtk+ 2, module name was gtk rather than gi.repository.Gtk.
Then you can quit wit with:
Gtk.main_quit
def main_quit()
The Gtk.main_quit() function terminates the current main loop level
started by the most recent call to the Gtk.main() function. The
nesting level of the main loop is reduced by calling this function.
You can have several nested main loops, in which case, you'd have to quit each of those.
Alternatively you can also use gtk_dialog.run() then default action for a button is to exit the loop.
GTK+ (as most UI toolkits) is event-based. That means it runs internal "event loop" - a loop that collects and processes events, such as handling user input and redrawing windows. All event handlers are dispatched from main loop. In order to process events, loop must be "spinning".
In your example, you are blocking main loop:
def on_button1_clicked(self, widget):
while(not self.record):
print 'button1 clicked'
as long as this function does not finish, control does not return to main loop so it cannot process other events, or redraw windows.
You can add this snippet form PyGTK FAQ in order to allow main loop to process event in the meantime:
while gtk.events_pending():
gtk.main_iteration(False)
I have a tkinter gui that has a button that starts a process. During this process there is an if statement, if this statement is true then then the process ends. When the process ends I want the GUI to be kept open and not show an error. I've tried os._exit() but it closes the gui as well.
from Tkinter import *
import tkMessageBox
def Program():
#Process
#Process
if #something happens#:
#Stop process but keep gui open and dont show errors
root = Tk()
root.title("GUI")
root.geometry('450x300+200+200')
labelText=StringVar()
labelText.set("Program")
label1=Label(root,textvariable=labelText,height=4)
label1.pack()
mbutton=Button(text='Start Program',command=Model).pack()
root.mainloop()
You could run GUI in the main thread and put the part that should terminate independently in a background thread. Add try/except in the thread to suppress traceback e.g.:
import threading
def bgthread(gui_ready, result_queue):
gui_ready.wait()
while True:
try:
# do some work ...
result_queue.put(result) # GUI gets results e.g.,
# via q.get_nowait() in a
# widget.after() callback
if something_happened():
break # exit
except: #NOTE: don't use bare except unless it is absolutely necessary
logger.error() # log to file
break # exit
# setup logging
# ...
ready = threading.Event()
q = Queue.Queue()
threading.Thread(target=bgthread, args=(ready,q)).start()
# setup gui here
...
root.mainloop() # call ready.set() in some GUI code then it is ready
Python code worked using geo_pythoncl suggestion of using return.
from Tkinter import *
import tkMessageBox
def Program():
#Process
#Process
if #something happens#:
#Stop process but keep gui open and dont show errors
return
root = Tk()
root.title("GUI")
root.geometry('450x300+200+200')
labelText=StringVar()
labelText.set("Program")
label1=Label(root,textvariable=labelText,height=4)
label1.pack()
mbutton=Button(text='Start Program',command=Model).pack()
root.mainloop()