PyQt app.exec() seems to be non-blocking - python

I have a quite complicated PyQt app (Qt5, running in Spyder), where at the end I do
def main():
from PyQt5 import QtWidgets
if not QtWidgets.QApplication.instance():
app = QtWidgets.QApplication(sys.argv)
else:
app = QtWidgets.QApplication.instance()
main_window = MainWindow()
main_window.show()
status = app.exec_()
print status
sys.exit(0)
if __name__ == "__main__":
main()
(The if-else check is needed because of this(second answer).) When I run this code, my app shows, and the status code -1 is printed at the same time (due to a raised error in spyder/utils/ipython/start_kernel.py). My question is, why this error is printed at all, because I thougt that app.exec_() is a blocking call and the status is not returned until the app is exited somehow. Is this due to Spyder running its own QApplication?

It is not possible to execute the application event-loop more than once. This is easy enough to test with a simple script:
import sys
from PyQt5 import QtCore, QtWidgets
app = QtWidgets.QApplication(sys.argv)
btn = QtWidgets.QPushButton('Test')
btn.clicked.connect(lambda: print(QtWidgets.QApplication.exec_()))
btn.show()
sys.exit(app.exec_())
Output:
QCoreApplication::exec: The event loop is already running
-1
So, if the event-loop is already running, exec just returns immediately without blocking.
(NB: obviously you will need to run the above script in a normal console to test it properly).

Related

Execute long running code at program startup after complete GUI is rendered

I have an application that can be started from the command line with an optional filename as argument. If present this file should be loaded at startup. Since the processing of the file takes some time, fileOpen() blocks the program and shows a loading indicator.
During normal operation this is ok. However, when I try to do the same at startup (as shown below), the outline of the window is present after show() but its contents is not rendered up until app.exec_().
My Question: How do I handle such a situation?
I cannot put fileOpen() before app.exec_() because then the GUI is not yet rendered completely. And I cannot inform the user that the loading is still processed.
I cannot put ? fileOpen() after app.exec_() because it would not be executed untill the program finishes.
Example Code:
def main(args):
app = QtGui.QApplication()
mainwindow = MainWindow()
mainwindow.show()
if args.filename:
mainwindow.fileOpen(args.filename)
ret_val = app.exec_()
sys.exit(ret_val)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('filename', help='(optional) file to load at startup')
args = parser.parse_args()
main(args)
I have found that a single-shot timer can solve this problem, but I have only tested it on Linux with the Openbox window manager, so I cannot guarantee that it will work on all platforms. You may need to adjust the duration of the timeout to get this to work on your system.
Here is a simple demo that works for me:
import sys
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.edit = QtWidgets.QTextEdit(self)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.edit)
def fileOpen(self, path):
QtWidgets.qApp.setOverrideCursor(QtCore.Qt.WaitCursor)
QtCore.QThread.sleep(3)
self.edit.setText(open(path).read())
QtWidgets.qApp.restoreOverrideCursor()
def main():
app = QtWidgets.QApplication(sys.argv)
mainwindow = MainWindow()
mainwindow.setGeometry(600, 100, 300, 200)
mainwindow.show()
QtCore.QTimer.singleShot(50, lambda: mainwindow.fileOpen(__file__))
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Note: This solution does not work for Linux(X11) (see comment and answer by ekhumoro)
Thanks for all the answers. While each of them has some drawbacks (will discuss it below), they brought me to the correct solution:
Call qApp.processEvents() after mainwindow.show():
def main(args):
app = QtGui.QApplication()
mainwindow = MainWindow()
mainwindow.show()
qApp.processEvents()
if args.filename:
mainwindow.fileOpen(args.filename)
ret_val = app.exec_()
sys.exit(ret_val)
Reason: It's exactly what we want to do:
We want to process events related to the drawing of the main window.
Then execute our custom code.
Then continue with the normal event loop.
Discussion of the alternative suggestions:
Why I don't call qApp.processEvents() in fileOpen(): This will be active for all fileOpen() calls. Processing other events during a long running file open call, may result in unexpected behavior if the application is not designed with this in mind, e.g. you could issue a second fileOpen() while the first is running.
Why I don't use a timer to perform fileOpen(): I want to execute the code after the GUI is completely loaded but before any user input. A timer just approixates the correct execution order. Additionally, the correct delay can vary depending on CPU, system usage and other factors, making this solution not very robust.

Run pyQT GUI main app in seperate Thread

I am trying to add a PyQt GUI console in my already established application. But the PyQt GUI blocks the whole application making it unable to do rest of the work. I tried using QThread, but that is called from the mainWindow class. What I want is to run the MainWindow app in separate thread.
def main()
app = QtGui.QApplication(sys.argv)
ex = Start_GUI()
app.exec_() #<---------- code blocks over here !
#After running the GUI, continue the rest of the application task
doThis = do_Thread("doThis")
doThis.start()
doThat = do_Thread("doThat")
doThat.start()
My application already uses Python Threads, So my question is, what is the best approach to achieve this process in a threaded form.
One way of doing this is
import threading
def main()
app = QtGui.QApplication(sys.argv)
ex = Start_GUI()
app.exec_() #<---------- code blocks over here !
#After running the GUI, continue the rest of the application task
t = threading.Thread(target=main)
t.daemon = True
t.start()
doThis = do_Thread("doThis")
doThis.start()
doThat = do_Thread("doThat")
doThat.start()
this will thread your main application to begin with, and let you carry on with all the other stuff you want to do after in the code below.

Getting PySide Hello App to run under Canopy

A Canopy user here learning about PySide. When I run the demo code below, QApplication complains the event loop is already running.'
import sys
from PySide.QtCore import *
from PySide.QtGui import *
# Create a Qt application
#app = QApplication(sys.argv) #QApplication complains an instance already exists
app = QApplication.instance() #So we just ask for the instance.
#app.aboutToQuit.connect(app.deleteLater)
# Create a Label and show it
label = QLabel("Hello World")
label.show()
# Enter Qt application main loop
app.exec_()
sys.exit()
So how can I get this simple code to run?
Yes, Pylab is a mode of IPython which starts an event loop for the IPython front end so that you can interact at the IPython command line with your GUI.
Here's an simple example of code which will run with or without Pylab.
import sys
from PySide import QtGui
app = QtGui.QApplication.instance()
standalone = app is None
if standalone:
app = QtGui.QApplication(sys.argv)
wid = QtGui.QWidget()
wid.resize(250,150)
wid.setWindowTitle('Simple')
wid.show()
if standalone:
sys.exit(app.exec_())
else:
print "We're back with the Qt window still active"

pyside second qt gui doesn't show up

I have a Qapplication in my python script that gives a logingui to my chat server.
When the login is complete I want to call upon my chat gui. To achieve this I've used the following
code:
app = QApplication(sys.argv)
form = LoginWindow()
form.show()
app.exec_()
#login done
form = ChatWindow()
form.show()
app.exec_()
This worked when I fired it up with an "empty" gui of the chat. So only the necessary things in it for it to boot up. However when I start connecting signals and stuff the second window just doesn't show up anymore. The console prints a statement from the beginning of the init but after that it falls silent and no gui is present.
Does anyone know how I can fix this weird problem? How is switching a form supposed to be done?
The login window should be a subclass of QDialog, so that it can be run separately from the main application. A QDialog has its own event loop, and provides a return code that can be used to check which action was taken by the user.
So, given this, your code would become:
app = QApplication(sys.argv)
dialog = LoginWindow()
if dialog.exec_() == QDialog.Accepted:
window = ChatWindow()
window.show()
app.exec_()
else:
print('Login cancelled')

Killing GUI on interactive console exit while running PyQt in a separate thread

I wanted to write a PyQt app that would spawn its window in a separate thread so I could mess with its data structures in an interactive shell while it's running. Unfortunately, an attempt to do exit() inside the shell does not kill the application, probably because the Qt thread is still running. How do I force the Python shell to die once I pressed CTR+D or typed exit() or typed exit in the console? Here is the testcase:
#!/usr/bin/python -i
from PyQt4 import QtGui
class MainWindow(QtGui.QMainWindow):
pass
def main():
import sys
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
import threading
threading.Thread(None, main).start()

Categories