gtk infinite loop exit - python

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)

Related

tkinter, master window does not loop after I start a thread [duplicate]

My interface is freezing on pressing the button. I am using threading but I am not sure why is still hanging. Any help will be appreciated. Thanks in advance
class magic:
def __init__(self):
self.mainQueue=queue.Queue()
def addItem(self,q):
self.mainQueue.put(q)
def startConverting(self,funcName):
if(funcName=="test"):
while not self.mainQueue.empty():
t = Thread(target = self.threaded_function)
t.start()
t.join()
def threaded_function(self):
time.sleep(5)
print(self.mainQueue.get())
m=magic()
def helloCallBack():
m.addItem("asd")
m.startConverting("test") //this line of code is freezing
B = tkinter.Button(top, text ="Hello", command = helloCallBack)
B.pack()
top.mainloop()
Here's a recipe for doing an asynchronous task with a tkinter-based GUI. I adapted it from a recipe in the cited book. You should be able to modify it to do what you need.
To keep the GUI responsive requires not interfering with its mainloop() by doing something like join()ing a background thread—which makes the GUI "hang" until the thread is finished. This is accomplished by using the universal after() widget method to poll a Queue at regular intervals.
# from "Python Coobook 2nd Edition", section 11.9, page 439.
# Modified to work in Python 2 & 3.
from __future__ import print_function
try:
import Tkinter as tk, time, threading, random, Queue as queue
except ModuleNotFoundError: # Python 3
import tkinter as tk, time, threading, random, queue
class GuiPart(object):
def __init__(self, master, queue, end_command):
self.queue = queue
# Set up the GUI
tk.Button(master, text='Done', command=end_command).pack()
# Add more GUI stuff here depending on your specific needs
def processIncoming(self):
""" Handle all messages currently in the queue, if any. """
while self.queue.qsize():
try:
msg = self.queue.get_nowait()
# Check contents of message and do whatever is needed. As a
# simple example, let's print it (in real life, you would
# suitably update the GUI's display in a richer fashion).
print(msg)
except queue.Empty:
# just on general principles, although we don't expect this
# branch to be taken in this case, ignore this exception!
pass
class ThreadedClient(object):
"""
Launch the main part of the GUI and the worker thread. periodic_call()
and end_application() could reside in the GUI part, but putting them
here means that you have all the thread controls in a single place.
"""
def __init__(self, master):
"""
Start the GUI and the asynchronous threads. We are in the main
(original) thread of the application, which will later be used by
the GUI as well. We spawn a new thread for the worker (I/O).
"""
self.master = master
# Create the queue
self.queue = queue.Queue()
# Set up the GUI part
self.gui = GuiPart(master, self.queue, self.end_application)
# Set up the thread to do asynchronous I/O
# More threads can also be created and used, if necessary
self.running = True
self.thread1 = threading.Thread(target=self.worker_thread1)
self.thread1.start()
# Start the periodic call in the GUI to check the queue
self.periodic_call()
def periodic_call(self):
""" Check every 200 ms if there is something new in the queue. """
self.master.after(200, self.periodic_call)
self.gui.processIncoming()
if not self.running:
# This is the brutal stop of the system. You may want to do
# some cleanup before actually shutting it down.
import sys
sys.exit(1)
def worker_thread1(self):
"""
This is where we handle the asynchronous I/O. For example, it may be
a 'select()'. One important thing to remember is that the thread has
to yield control pretty regularly, be it by select or otherwise.
"""
while self.running:
# To simulate asynchronous I/O, create a random number at random
# intervals. Replace the following two lines with the real thing.
time.sleep(rand.random() * 1.5)
msg = rand.random()
self.queue.put(msg)
def end_application(self):
self.running = False # Stops worker_thread1 (invoked by "Done" button).
rand = random.Random()
root = tk.Tk()
client = ThreadedClient(root)
root.mainloop()
For anyone having a problem with sys.exit(1) in #martineau's code - if you replace sys.exit(1) with self.master.destroy() the program ends gracefully. I lack the reputation to add a comment, hence the seperate answer.

How do I exit my python3 application cleanly from asyncio event loop run_forever() when user clicks tkinter root window close box?

I'm trying to make a python3 application for my Raspberry Pi 4B and I have the tkinter windows working fine, but need to add asynchronous handling to allow tkinter widgets to respond while processing asynchronous actions initiated by the window's widgets.
The test code is using asyncio and tkinter. However, without root.mainloop(), since asyncio loop.run_forever() is called at the end instead. The idea is that when the user clicks the main window's close box, RequestQuit() gets called to set the quitRequested flag and then when control returns to the event loop, root.after_idle(AfterIdle) would cause AfterIdle to be called, where the flag is checked and if true, the event loop is stopped, or that failing, the app is killed with exit(0).
The loop WM_DELETE_WINDOW protocol coroutine RequestQuit is somehow not getting called when the user clicks the main window close box, so the AfterIdle coroutine never gets the flag to quit and I have to kill the app by quitting XQuartz.
I'm using ssh via Terminal on MacOS X Big Sur 11.5.2, connected to a Raspberry Pi 4B with Python 3.7.3.
What have I missed here?
(I haven't included the widgets or their handlers or the asynchronous processing here, for brevity, since they aren't part of the problem at hand.)
from tkinter import *
from tkinter import messagebox
import aiotkinter
import asyncio
afterIdleProcessingIntervalMsec = 500 # Adjust for UI responsiveness here.
busyProcessing = False
quitRequested = False
def RequestQuit():
global quitRequested
global busyProcessing
if busyProcessing:
answer = messagebox.askquestion('Exit application', 'Do you really want to abort the ongoing processing?', icon='warning')
if answer == 'yes':
quitRequested = True
def AfterIdle():
global quitRequested
global loop
global root
if not quitRequested:
root.after(afterIdleProcessingIntervalMsec, AfterIdle)
else:
print("Destroying GUI at: ", time.time())
try:
loop.stop()
root.destroy()
except:
exit(0)
if __name__ == '__main__':
global root
global loop
asyncio.set_event_loop_policy(aiotkinter.TkinterEventLoopPolicy())
loop = asyncio.get_event_loop()
root = Tk()
root.protocol("WM_DELETE_WINDOW", RequestQuit)
root.after_idle(AfterIdle)
# Create and pack widgets here.
loop.run_forever()
The reason why your program doesn't work is that there is no Tk event loop, or its equivalent. Without it, Tk will not process events; no Tk callback functions will run. So your program doesn't respond to the WM_DELETE_WINDOW event, or any other.
Fortunately Tk can be used to perform the equivalent of an event loop as an asyncio.Task, and it's not even difficult. The basic concept is to write a function like this, where "w" is any tk widget:
async def new_tk_loop():
while some_boolean:
w.update()
await asyncio.sleep(sleep_interval_in_seconds)
This function should be created as an asyncio.Task when you are ready to start processing tk events, and should continue to run until you are ready to stop doing that.
Here is a class, TkPod, that I use as the basic foundation of any Tk + asyncio program. There is also a trivial little demo program, illustrating how to close the Tk loop from another Task. If you click the "X" before 5 seconds pass, the program will close immediately by exiting the mainloop function. After 5 seconds the program will close by cancelling the mainloop task.
I use a default sleep interval of 0.05 seconds, which seems to work pretty well.
When exiting such a program there are a few things to think about.
When you click on the "X" button on the main window, the object sets its app_closing variable to false. If you need to do some other clean-up, you can subclass Tk and over-ride the method close_app.
Exiting the mainloop doesn't call the destroy function. If you need to do that, you must do it separately. The class is a context manager, so you can make sure that destroy is called using a with block.
Like any asyncio Task, mainloop can be cancelled. If you do that, you need to catch that exception to avoid a traceback.
#! python3.8
import asyncio
import tkinter as tk
class TkPod(tk.Tk):
def __init__(self, sleep_interval=0.05):
self.sleep_interval = sleep_interval
self.app_closing = False
self.loop = asyncio.get_event_loop()
super().__init__()
self.protocol("WM_DELETE_WINDOW", self.close_app)
# Globally suppress the Tk menu tear-off feature
# In the following line, "*tearOff" works as documented
# while "*tearoff" does not.
self.option_add("*tearOff", 0)
def __enter__(self):
return self
def __exit__(self, *_x):
self.destroy()
def close_app(self):
self.app_closing = True
# I don't know what the argument n is for.
# I include it here because pylint complains otherwise.
async def mainloop(self, _n=0):
while not self.app_closing:
self.update()
await asyncio.sleep(self.sleep_interval)
async def main():
async def die_in5s(t):
await asyncio.sleep(5.0)
t.cancel()
print("It's over...")
with TkPod() as root:
label = tk.Label(root, text="Hello")
label.grid()
t = asyncio.create_task(root.mainloop())
asyncio.create_task(die_in5s(t))
try:
await t
except asyncio.CancelledError:
pass
if __name__ == "__main__":
asyncio.run(main())

Update a Gtk.ProgressBar from another thread or process

I have a GUI with a progressbar. It should show the progress of the work a second thread does. I would like to have something like an event the thread can send to the GUIs progressbar immediatly on each step of the work. But I don't see how this could be done.
Python itself offers a Event class for threading situations. But it would block the GUI main thread because of the Event.wait() methode.
How does it change the situaton and possible solutions if the second thread is a process?
My example here is based on PyGObject (Pythons Gtk) but is related to all other GUI libraries, too.
The current solution works but it is IMO just a workaround. The GUI (as main thread) and the second (worker) thread sharing data via a threadsafe queue.Queue. There is a timer event in the GUI thread checking the qeueu in **fixed intervalls* for new data from the thread and updates the progressbar.
#!/usr/bin/env python3
import time
import threading
import queue
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib
class MyThread(threading.Thread):
def __init__(self, queue, n_tasks):
threading.Thread.__init__(self)
self._queue = queue
self._max = n_tasks
def run(self):
for i in range(self._max):
# simulate a task
time.sleep(1)
# put something in the data queue
self._queue.put(1)
class MyWindow(Gtk.Window):
def __init__(self, n_tasks):
Gtk.Window.__init__(self)
# max and current number of tasks
self._max = n_tasks
self._curr = 0
# queue to share data between threads
self._queue = queue.Queue()
# gui: progressbar
self._bar = Gtk.ProgressBar(show_text=True)
self.add(self._bar)
self.connect('destroy', Gtk.main_quit)
# install timer event to check the queue for new data from the thread
GLib.timeout_add(interval=250, function=self._on_timer)
# start the thread
self._thread = MyThread(self._queue, self._max)
self._thread.start()
def _on_timer(self):
# if the thread is dead and no more data available...
if not self._thread.is_alive() and self._queue.empty():
# ...end the timer
return False
# if data available
while not self._queue.empty():
# read data from the thread
self._curr += self._queue.get()
# update the progressbar
self._bar.set_fraction(self._curr / self._max)
# keep the timer alive
return True
if __name__ == '__main__':
win = MyWindow(30)
win.show_all()
Gtk.main()
Based on the comments of my question I modified my example. Please use this with caution because it is still unclear for me if the solution is thread safe or not.
I tried the GLib.idle_add() and removed my own timer and the queue. Attention: The docu is no correct about the signature/parameters of the methode. Originaly it is
idle_add(function, *user_data, **kwargs)
Possible solution for Threads
#!/usr/bin/env python3
import time
import threading
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib
class MyThread(threading.Thread):
def __init__(self, callback, n_tasks):
threading.Thread.__init__(self)
self._callback = callback
self._max = n_tasks
def run(self):
for i in range(self._max):
# simulate a task
time.sleep(1)
# increment/update progress
GLib.idle_add(self._callback)
class MyWindow(Gtk.Window):
def __init__(self, n_tasks):
Gtk.Window.__init__(self)
# max and current number of tasks
self._max = n_tasks
self._curr = 0
# gui: progressbar
self._bar = Gtk.ProgressBar(show_text=True)
self.add(self._bar)
self.connect('destroy', Gtk.main_quit)
# start the thread
self._thread = MyThread(self._update_progress, self._max)
self._thread.start()
def _update_progress(self):
# increment
self._curr += 1
# update the progressbar
self._bar.set_fraction(self._curr / self._max)
# end this event handler
return False
if __name__ == '__main__':
win = MyWindow(30)
win.show_all()
Gtk.main()
What GLib.idle_add() does?
I am not an expert or core developer of Gtk. In my understanding I would say you can _install__ event handler metodes into the Gtk main loop. In other words the second thread tells the Gtk main loop (which is the first thread) to call the givin methode when nothing else is todo (which is quit often in a GUI loop).
There is no solution for Process because they run in a separate Python interpreter instance. There is no way to call GLib.idle_add() between two process.
Your implementation is correct. You are processing the threaded commands, sharing the feedback in a Queue, and updating the progressbar from the main loop via the GLib.timeout_add().
Initially, this may seem like a complex way to do a simple progressbar update, but it is one of the only ways to spawn a child process and track the progress, all the while respecting the Gtk main loop.

Freezing/Hanging tkinter GUI in waiting for the thread to complete

My interface is freezing on pressing the button. I am using threading but I am not sure why is still hanging. Any help will be appreciated. Thanks in advance
class magic:
def __init__(self):
self.mainQueue=queue.Queue()
def addItem(self,q):
self.mainQueue.put(q)
def startConverting(self,funcName):
if(funcName=="test"):
while not self.mainQueue.empty():
t = Thread(target = self.threaded_function)
t.start()
t.join()
def threaded_function(self):
time.sleep(5)
print(self.mainQueue.get())
m=magic()
def helloCallBack():
m.addItem("asd")
m.startConverting("test") //this line of code is freezing
B = tkinter.Button(top, text ="Hello", command = helloCallBack)
B.pack()
top.mainloop()
Here's a recipe for doing an asynchronous task with a tkinter-based GUI. I adapted it from a recipe in the cited book. You should be able to modify it to do what you need.
To keep the GUI responsive requires not interfering with its mainloop() by doing something like join()ing a background thread—which makes the GUI "hang" until the thread is finished. This is accomplished by using the universal after() widget method to poll a Queue at regular intervals.
# from "Python Coobook 2nd Edition", section 11.9, page 439.
# Modified to work in Python 2 & 3.
from __future__ import print_function
try:
import Tkinter as tk, time, threading, random, Queue as queue
except ModuleNotFoundError: # Python 3
import tkinter as tk, time, threading, random, queue
class GuiPart(object):
def __init__(self, master, queue, end_command):
self.queue = queue
# Set up the GUI
tk.Button(master, text='Done', command=end_command).pack()
# Add more GUI stuff here depending on your specific needs
def processIncoming(self):
""" Handle all messages currently in the queue, if any. """
while self.queue.qsize():
try:
msg = self.queue.get_nowait()
# Check contents of message and do whatever is needed. As a
# simple example, let's print it (in real life, you would
# suitably update the GUI's display in a richer fashion).
print(msg)
except queue.Empty:
# just on general principles, although we don't expect this
# branch to be taken in this case, ignore this exception!
pass
class ThreadedClient(object):
"""
Launch the main part of the GUI and the worker thread. periodic_call()
and end_application() could reside in the GUI part, but putting them
here means that you have all the thread controls in a single place.
"""
def __init__(self, master):
"""
Start the GUI and the asynchronous threads. We are in the main
(original) thread of the application, which will later be used by
the GUI as well. We spawn a new thread for the worker (I/O).
"""
self.master = master
# Create the queue
self.queue = queue.Queue()
# Set up the GUI part
self.gui = GuiPart(master, self.queue, self.end_application)
# Set up the thread to do asynchronous I/O
# More threads can also be created and used, if necessary
self.running = True
self.thread1 = threading.Thread(target=self.worker_thread1)
self.thread1.start()
# Start the periodic call in the GUI to check the queue
self.periodic_call()
def periodic_call(self):
""" Check every 200 ms if there is something new in the queue. """
self.master.after(200, self.periodic_call)
self.gui.processIncoming()
if not self.running:
# This is the brutal stop of the system. You may want to do
# some cleanup before actually shutting it down.
import sys
sys.exit(1)
def worker_thread1(self):
"""
This is where we handle the asynchronous I/O. For example, it may be
a 'select()'. One important thing to remember is that the thread has
to yield control pretty regularly, be it by select or otherwise.
"""
while self.running:
# To simulate asynchronous I/O, create a random number at random
# intervals. Replace the following two lines with the real thing.
time.sleep(rand.random() * 1.5)
msg = rand.random()
self.queue.put(msg)
def end_application(self):
self.running = False # Stops worker_thread1 (invoked by "Done" button).
rand = random.Random()
root = tk.Tk()
client = ThreadedClient(root)
root.mainloop()
For anyone having a problem with sys.exit(1) in #martineau's code - if you replace sys.exit(1) with self.master.destroy() the program ends gracefully. I lack the reputation to add a comment, hence the seperate answer.

PyQt4: How to pause a Thread until a signal is emitted?

I have the following pyqtmain.py:
#!/usr/bin/python3
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from pyqtMeasThread import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
self.qt_app = QApplication(sys.argv)
QMainWindow.__init__(self, parent)
buttonWidget = QWidget()
rsltLabel = QLabel("Result:")
self.rsltFiled = QLineEdit()
self.buttonStart = QPushButton("Start")
verticalLayout = QVBoxLayout(buttonWidget)
verticalLayout.addWidget(rsltLabel)
verticalLayout.addWidget(self.rsltFiled)
verticalLayout.addWidget(self.buttonStart)
butDW = QDockWidget("Control", self)
butDW.setWidget(buttonWidget)
self.addDockWidget(Qt.LeftDockWidgetArea, butDW)
self.mthread = QThread() # New thread to run the Measurement Engine
self.worker = MeasurementEngine() # Measurement Engine Object
self.worker.moveToThread(self.mthread)
self.mthread.finished.connect(self.worker.deleteLater) # Cleanup after thread finished
self.worker.measure_msg.connect(self.showRslt)
self.buttonStart.clicked.connect(self.worker.run)
# Everything configured, start the worker thread.
self.mthread.start()
def run(self):
""" Show the window and start the event loop """
self.show()
self.qt_app.exec_() # Start event loop
#pyqtSlot(str)
def showRslt(self, mystr):
self.rsltFiled.setText(mystr)
def main():
win = MainWindow()
win.run()
if __name__ == '__main__':
main()
And another thread script performing the actual measurement:
from PyQt4.QtCore import *
import time
class MeasurementEngine(QObject):
measure_msg = pyqtSignal(str)
def __init__(self):
QObject.__init__(self) # Don't forget to call base class constructor
#pyqtSlot()
def run(self):
self.measure_msg.emit('phase1')
time.sleep(2) # here I would like to make it as an interrupt
self.measure_msg.emit('phase2')
What this code does now is that after the Start button is pressed, the function run in the thread will be executed. However, actually in the function run, there are two phases of the measurement. Right now I used an time delay.
But what I would like to implement actually is that after the 'phase1' measurement is done. A message box will be popped up, and at the same time, the thread will be paused/held. Until the user closed the message box, then the thread function will be resumed.
Use a QWaitCondition from the QtCore module. Using a mutex lock, you set the background thread to wait/sleep until the foreground thread wakes it back up. Then it will continue doing its work from there.
#!/usr/bin/python3
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from pyqtMeasThread import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
self.qt_app = QApplication(sys.argv)
QMainWindow.__init__(self, parent)
buttonWidget = QWidget()
rsltLabel = QLabel("Result:")
self.rsltFiled = QLineEdit()
self.buttonStart = QPushButton("Start")
verticalLayout = QVBoxLayout(buttonWidget)
verticalLayout.addWidget(rsltLabel)
verticalLayout.addWidget(self.rsltFiled)
verticalLayout.addWidget(self.buttonStart)
butDW = QDockWidget("Control", self)
butDW.setWidget(buttonWidget)
self.addDockWidget(Qt.LeftDockWidgetArea, butDW)
self.mutex = QMutex()
self.cond = QWaitCondition()
self.mthread = QThread() # New thread to run the Measurement Engine
self.worker = MeasurementEngine(self.mutex, self.cond) # Measurement Engine Object
self.worker.moveToThread(self.mthread)
self.mthread.finished.connect(self.worker.deleteLater) # Cleanup after thread finished
self.worker.measure_msg.connect(self.showRslt)
self.buttonStart.clicked.connect(self.worker.run)
# Everything configured, start the worker thread.
self.mthread.start()
def run(self):
""" Show the window and start the event loop """
self.show()
self.qt_app.exec_() # Start event loop
# since this is a slot, it will always get run in the event loop in the main thread
#pyqtSlot(str)
def showRslt(self, mystr):
self.rsltFiled.setText(mystr)
msgBox = QMessageBox(parent=self)
msgBox.setText("Close this dialog to continue to Phase 2.")
msgBox.exec_()
self.cond.wakeAll()
def main():
win = MainWindow()
win.run()
if __name__ == '__main__':
main()
And:
from PyQt4.QtCore import *
import time
class MeasurementEngine(QObject):
measure_msg = pyqtSignal(str)
def __init__(self, mutex, cond):
QObject.__init__(self) # Don't forget to call base class constructor
self.mtx = mutex
self.cond = cond
#pyqtSlot()
def run(self):
# NOTE: do work for phase 1 here
self.measure_msg.emit('phase1')
self.mtx.lock()
try:
self.cond.wait(self.mtx)
# NOTE: do work for phase 2 here
self.measure_msg.emit('phase2')
finally:
self.mtx.unlock()
Your timing is a little bit off in all this though. You create the app and start the thread before you even show your window. Thus, the message box will pop up before the main window even pops up. To get the right sequence of events, you should start your thread as part of the run method of your MainWindow, after you have already made the main window visible. If you want the wait condition to be separate from the setting of the messages, you may need a separate signal and slot to deal with that.
You can't display a QDialog from within a QThread. All GUI related stuff must be done in the GUI thread (the one that created the QApplication object). What you could do is to use 2 QThread:
1st: perform phase1. You can connect the finished signal of this QThread to a slot in the QMainWindow that will display the popup (using QDialog.exec_() so it will be modal).
2nd: perform phase2. You create the QThread after the popup shown here above has been closed.
Your thread can emit a signal to the main window to show the dialog.
If you don't want to close the thread while the dialog is open, the thread could enter a while loop for waiting. In the while loop it can continuously check a variable which the main thread can set to true after the dialog is finished.
This might not be the cleanest solution, but it should work.
To clarify my answer a bit, I added some pseudo code. What you have to care about is how you share the dialog_closed variable. You could e.g. use a member variable of the thread class.
Thread:
emit_signal
dialog_closed = False
while not dialog_closed:
pass
go_on_with_processing
MainThread:
def SignalRecieved():
open_dialog
dialog_closed = True
I recently had to solve pretty much this problem, did a little research and discovered an elegant technique that seems to work reliably. I didn't need the full complexity detailed there, so here's an outline of the steps I took.
My GUI class defines, as class attributes, two signals.
oyn_sig = pyqtSignal(str) # Request for operator yes/no
ryn_sig = pyqtSignal(bool) # Response to yes/no request
Inside the method that initialises the GUI components this signal is connected to the GUI instance's signal handler.
self.oyn_sig.connect(self.operator_yes_no)
Here's the code for the handler method of the GUI:
#pyqtSlot(str)
def operator_yes_no(self, msg):
"Asks the user a `yes/no question on receipt of a signal then signal a bool answer.`"
answer = QMessageBox.question(None,
"Confirm Test Sucess",
msg,
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
# Signal the caller that the result was received.
self.ryn_sig.emit(answer==QMessageBox.Yes)
As usual the GUI is running in the main thread, and so it needs to be signalled from the thread doing the work in the background. In turn, once it's received the operator's response it raises a response signal to the originating thread.
The worker thread uses the following function to get an operator response.
def operator_yes_no(self, msg):
loop = LoopSpinner(self.gui, msg)
loop.exec_()
return loop.result
This creates a LoopSpinner object and starts executing its event loop, thereby suspend the current thread's event loop until the "inner thread" terminates. Most of the smarts are hidden inside the LoopSpinner class, which should probably have been better named. Here's its definition.
class LoopSpinner(QEventLoop):
def __init__(self, gui, msg):
"Ask for an answer and communicate the result."
QEventLoop.__init__(self)
gui.ryn_sig.connect(self.get_answer)
gui.oyn_sig.emit(msg)
#pyqtSlot(bool)
def get_answer(self, result):
self.result = result
self.quit()
A LoopSpinner instance connects the response signal to its get_answer method and emits the question signal. When the signal is received the answer is stored as an attribute value and the loop quits. The loop is still referenced by its caller, which can safely access the result attribute before the instance is garbage collected.

Categories

Resources