pyside second qt gui doesn't show up - python

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')

Related

How to quit Qt application before main window is displayed

On launch, my Qt application displays a dialog box with some options, before displaying the main window. The dialog box has a 'Start' and 'Cancel' button. The same dialog box is used at a later time, after the main window is displayed, when the user clicks on a 'New Game' button.
I'm trying to have the 'Cancel' button quit the application if it's the only interface element being displayed (i.e. on application launch). In my current code, however, the main window is still displayed.
If I replace self.view.destroy() with self.view.deleteLater() the main window briefly flashes into existence before disappearing (and quitting properly), but this can't be the solution.
If I move the view.show() call inside the dialog.exec_() block, it doesn't work either. Mainly because I would be calling view.show() every time the dialog is displayed again from within the main window, but also because even in this case the app doesn't really quit. The main window, in this case, is not displayed but the process is keeps running (Python application icon still visible in the Dock).
What am I doing wrong here? I've read other similar questions but I don't understand how to apply these solutions in my case.
(PySide2 5.15.1 on macOS 10.15.6)
class App:
def __init__(self, app, game, view):
self.app = app
self.game = game
self.view = view
# Display the dialog
# Same callback is bound to a QPushButton in MainWindow
self.cb_start_dialog()
def cb_start_dialog(self):
# Do some initialisation
dialog = DialogNewGame() # A subclass of QDialog
if dialog.exec_():
# Setup the interface
else:
if not self.view.isVisible():
# Condition evaluates correctly
# (False on app launch,
# True if main window is displayed)
self.view.destroy()
self.app.quit() # Doesn't work, main window still displayed
def main():
application = QApplication()
view = MainWindow() # A QWidget with the main window
model = Game() # Application logic
App(application, model, view)
view.show()
application.exec_()
If the code is analyzed well, it is observed that "quit" is invoked before the eventloop starts, so it makes no sense to terminate an eventloop that never started. The solution is to invoke X an instant after the eventloop starts. On the other hand, the quit method is static so it is not necessary to access "self.app"
from PySide2.QtCore import QTimer
from PySide2.QtWidgets import QApplication, QDialog, QMainWindow
class MainWindow(QMainWindow):
pass
class DialogNewGame(QDialog):
pass
class Game:
pass
class App:
def __init__(self, game, view):
self.game = game
self.view = view
QTimer.singleShot(0, self.cb_start_dialog)
def cb_start_dialog(self):
dialog = DialogNewGame()
if dialog.exec_():
pass
else:
QApplication.quit()
def main():
application = QApplication()
view = MainWindow()
model = Game()
app = App(model, view)
view.show()
application.exec_()
if __name__ == "__main__":
main()

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

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).

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.

QWebView in PyQt4 doesn't open when I put it in a function

I am trying something basic with QWebView in PyQt4. I want to just load a URL. What is strange is that, when I put the QWebView in a function call, it doesn't work, but when it's inline, it does work.
So, the following code works as expected:
if __name__ == '__main__':
app = QApplication(sys.argv)
web = QWebView()
web.load(QUrl('http://www.google.com'))
web.setFixedSize(500, 500)
web.show()
sys.exit(app.exec_())
However, when I move the QWebView code into a function, as shown below, the web view never opens. Instead, the application just appears to hang.
def openPage():
web = QWebView()
web.load(QUrl('http://www.google.com'))
web.setFixedSize(500, 500)
web.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
openPage()
sys.exit(app.exec_())
What is going on here? This doesn't seem to make sense.
In openPage you bind the web view object to a local variable web. The web view is automatically destroyed when the variable goes out of scope (when the function returns). You need to keep a reference to the view, perhaps by return like this:
def openPage():
web = QWebView()
web.load(QUrl('http://www.google.com'))
web.setFixedSize(500, 500)
web.show()
return web
if __name__ == '__main__':
app = QApplication(sys.argv)
web = openPage()
sys.exit(app.exec_())

How to call multiple Dialogs in PyQt?

I have a main dialog and on that dialog there is a button. When the button is clicked, I want to open an another dialog.
Main Dialog code (Function which is called when the button is clicked in the main dialog):
def add_host(self):
x=add_host.Ui_Dialog1()
x.main()
default function:
if __name__ == "__main__":
import sys
global app
app = QtGui.QApplication(sys.argv)
Dialog = QtGui.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())
Secondary dialog (add_host.py) code snapshot:
def main(self):
app1 = QtGui.QApplication(sys.argv)
Dialog1 = QtGui.QDialog()
ui1 = Ui_Dialog1()
ui1.setupUi1(Dialog1)
Dialog1.show()
sys.exit(app.exec_())
So when I run the code, it opens the secondary dialog but when I close it, it just freezes, and I get this error message:
File "testbot.py", line 175, in add_host
x.main()
File "/home/ppp/ppp/add_host.py", line 74, in main
sys.exit(app.exec_())
NameError: global name 'app' is not defined
Which does make sense, but I have no idea how to resolve it. I try several combinations without success, including adding and deleting app.exec_().
You cannot create multiple QApplications inside of the same script and thread. You are only supposed to have one...
This should be more like:
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
Dialog = QtGui.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())
No global app. Although you should be doing your setupUI from within the class of your dialog.
Then when you want to show another dialog from with your app...say main(), you just create it and call show()
Here is a really basic example:
class Dialog(QDialog)
def __init__(self, parent):
super(Dialog, self).__init__(parent)
self.otherDialog = QDialog(parent=self)
self.otherDialog.show()
if __name__ == "__main__":
app = QApplication([])
dialog = Dialog()
dialog.show()
app.exec_()
You create a single QApplication and start its event loop by calling exec_(). From that point on, your main application is free to create more QWidgets. You never create another QApplication again at this point.
Also, I dont understand this part of your code:
def add_host(self):
x=add_host.Ui_Dialog1()
x.main()
The fact that you are calling a main() method on your UI object makes me think you are modifying the UI file and adding functionality to it, which you should not be doing. That ui file is subject to be overwritten every time you make changes in QT Designer and save out a new one. You should only be importing it and using its setupUI() method to apply it to your own custom classes.
A note about organization of your modules
When you are designing a PyQT application, you will always have a single entry point that will be called to start your app. This is the single and only location that should be creating your QApp and starting the event loop, and is usually done with in a if __name__ == "__main__" block to ensure its only done when its the main script. It should not be done within methods of your objects. For all your other modules where you define other Dialog, Widgets, etc, these should simply be classes that you import. As long as you have a running QApp, you are free to create and show these widgets.
Your code sample is a bit confusing - I don't understand why you have two mains, etc - anyway, maybe it's just a typo in add_host.py (app1.exec_() instead of app.exec_())
def main(self):
app1 = QtGui.QApplication(sys.argv)
...
sys.exit(app1.exec_())

Categories