How to quit Qt application before main window is displayed - python

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

Related

Switch back and forth between windows in pyqt5 while avoiding circular imports

So I have 2 windows I want to be able to switch between, Login and MainWindow, each one is QWidget in its own seperate file, loginUI.py and MainUI.py respectively.
I can easily switch from login to main upon correct authentification by creating a new instance of MainWindow. But in MainWindow I want to have a 'Disconnect' button that shows the Login screen.
Since both are in different files, importing in this scenario raises a circular import error in python.
Other approaches I tried are:
Using signals and handling them in an intermediate file. This is fine but as I add more buttons/ windows the file started to become a bit of a mess.
Passing the instance of Login to MainWindow.__init__(self, login), and just using self.login.show(). Which seems like a good way, but again as I add more and more windows, I'm scared it might affect performance as so many instances are just running in the background.
Is any of these the correcte way or am I missing an easier way
Edit:
login.py
from PyQt5.QtWidgets import QWidget, QPushButton, QLineEdit
from PyQt5.QtCore import QSize
from mainmenu import MainWindow
from PyQt5 import QtWidgets
import sys
class Login(QWidget):
def __init__(self):
QWidget.__init__(self)
self.setMinimumSize(QSize(300, 200))
self.setWindowTitle("Log in")
self.username = QLineEdit(self)
self.username.move(50, 10)
self.password = QLineEdit(self)
self.password.move(50, 40)
self.connect_button = QPushButton('Connect', self)
self.connect_button.move(50, 100)
self.connect_button.clicked.connect(self.handleConnexion)
def handleConnexion(self):
if self.username.text() == "admin" and self.password.text()=="1":
self.mw = MainWindow()
self.mw.show()
self.close()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWin = Login()
mainWin.show()
sys.exit( app.exec_() )
mainmenu.py
from PyQt5.QtCore import QSize
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(300, 200))
self.setWindowTitle("Main menu")
disconnect_button = QPushButton('Disconnect', self)
disconnect_button.clicked.connect(self.handeDC)
def handeDC(self):
pass
# here I either send a signal to handle it somewhere else
# or
# if i pass the login instance in __init__, just do login.show()
Note: since this is a common question, I'll provide a more broad answer that better reflects the object hierarchy.
Since the "main" window is, as the name suggests, the main one, the script containing it should be the main one, while the login window should be imported and eventually shown as required.
The hierarchy is important: you don't have to consider the order in which the windows are shown, but their relevance.
Considering this, the main script will:
create the main window;
show the login if required;
show the main window if the login is successful;
show again the login if the user disconnects;
clear the contents if the user has changed (*see below);
The above also shows why it's rarely a good idea to continuously create new instances of windows.
The login window should also be a QDialog, which makes things easier: the exec() method is "blocking" (for the function execution, not for the event loop), and waits until the dialog is accepted or *rejected.
main.py
from PyQt5.QtWidgets import *
from login import Login
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setMinimumSize(300, 200)
self.setWindowTitle('Main menu')
disconnect_button = QPushButton('Disconnect')
self.setCentralWidget(disconnect_button)
# only for a *persistent* login dialog (*see below)
self.login = Login()
disconnect_button.clicked.connect(self.disconnect)
def start(self):
# put here some function that might check for a 'previous' logged in
# state, possibly stored using QSettings.
# in this case, we just assume that the user has never previously
# logged in, so we automatically show the login window; if the above
# function returns True instead, we can safely show the main window
logged = False
if logged:
self.show()
else:
self.showLogin()
def disconnect(self):
self.hide()
self.showLogin()
def showLogin(self):
if self.login.exec():
self.show()
# alternatively (*see below):
login = Login()
if login.exec():
self.show()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.start()
sys.exit(app.exec())
login.py
from PyQt5.QtWidgets import *
class Login(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setMinimumSize(300, 200)
self.setWindowTitle('Log in')
self.username = QLineEdit()
self.password = QLineEdit()
self.password.setEchoMode(self.password.Password)
self.connect_button = QPushButton('Connect', enabled=False)
layout = QGridLayout(self)
layout.addWidget(QLabel('Username:'), 0, 0)
layout.addWidget(self.username, 0, 1)
layout.addWidget(QLabel('Password:'), 1, 0)
layout.addWidget(self.password, 1, 1)
layout.addWidget(self.connect_button, 2, 0, 1, 2)
self.connect_button.clicked.connect(self.handleConnexion)
self.username.textChanged.connect(self.checkFields)
self.password.textChanged.connect(self.checkFields)
def checkFields(self):
if self.username.text() and self.password.text():
self.connect_button.setEnabled(True)
else:
self.connect_button.setEnabled(False)
def handleConnexion(self):
if self.username.text() == 'admin' and self.password.text() == '1':
self.accept()
else:
QMessageBox.warning(self, 'Error',
'Invalid username or password!')
Notes:
the above code will always show the existing Login instance, so the username and password fields will "remember" the previous entries; if you don't want that, you can always call clear() on those fields by overriding the exec() (but remember to call the base implementation and return its result!); alternatively, don't create the self.login and always create a new, local instance of Login() in showLogin();
you shall always use layout managers, and never rely on fixed geometries;
QMainWindow should always have a central widget, creating children of a main window using it as the parent is discouraged (unless you really know what you're doing); if you need more widgets, use a basic QWidget, set a layout for it, add the children, and finally call setCentralWidget();
more complex hierarchies can require a "controller" (read more about the MVC pattern) to better organize the whole program and respect the OOP patterns; this is normally achieved by a basic class or by subclassing the QApplication;
about the last (*) point in my initial list, and related to what explained above: a "controller" could/should completely delete the previous "main window" (it would be both easier and safer) and eventually show a new instance whenever the user has disconnected;
Instead of closing the Login widget when creating the MainWindow widget, you could just hide the widget, which will save the overhead of creating a new instance and also keep the connected slots intact.
Then on your MainWindow you can create a diconnected signal that should be emited when the user clicks the disconnect button.
The login window can listen for the signal and call it's show method.
I made inline comments where I made changes in the example below:
mainwindow.py
from PyQt5.QtCore import pyqtSignal # added this
class MainWindow(QMainWindow):
disconnected = pyqtSignal() # added this
def __init__(self):
QMainWindow.__init__(self)
...
disconnect_button = QPushButton('Disconnect', self)
disconnect_button.clicked.connect(self.handeDC)
def handeDC(self):
# ... do some stuff
self.disconnected.emit() # added this
login.py
class Login(QWidget):
def __init__(self):
QWidget.__init__(self)
...
def handleConnexion(self):
if self.username.text() == "admin" and self.password.text()=="1":
self.mw = MainWindow()
self.mw.disconnected.connect(self.show) # added this
self.mw.show()
self.hide() # changed this
...

Open window application in current window

I want to open a game application replacing the menu window.
So I have a game application which I can start with:
subprocess.call(["mygameprogram", "argument one"])
This opens up another window, but I want to replace the qt window with the game window without closing the menu window.
This is my MainWindow class:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initMenu()
self.show()
def initMenu(self):
# load ui file
uic.loadUi("somefile.ui", self)
# add pushbutton
btn = QPushButton()
# add click listener
btn.clicked.connect(self.startLevel)
# add button to layout
self.layout_buttons.addWidget(btn)
def startLevel(self):
# open game, put the right code in here :D
pass
if __name__ == "__main__":
app = QApplication(sys.argv)
ex = MainWindow()
sys.exit(app.exec_())
Is there any solution for that? And if not, what should I do instead?
Thanks in advance

Click event is sent immediately after I start application (PySide and Python)

I have an application with a button for which the clicked signal is connected to a slot that opens a QFileDialog. I want to manipulate the state of the button (sender) within the slot depending on the actions taken by the user in the QFileDialog.
However, with the code I have presently, my application do not starts correctly. It starts immediately with QFileDialogOpen and I do not understand why. When I comment the line that connect the button's clicked signal to the slot, the application starts normally though.
How can I correctly pass the button as an argument when I want to connect a clicked signal of a button to a slot? Here is a MCWE of my problem:
from PySide import QtGui
import sys
class MyApplication(QtGui.QWidget):
def __init__(self, parent=None):
super(MyApplication, self).__init__(parent)
self.fileButton = QtGui.QPushButton('Select File')
self.fileButton.clicked.connect(self.select_file(self.fileButton))
layout = QtGui.QGridLayout()
layout.addWidget(self.fileButton)
self.setLayout(layout)
def select_file(self, button):
file_name = QtGui.QFileDialog.getOpenFileName()
if str(file_name[0]) is not "":
button.setEnabled(True)
else:
button.setDisabled(True)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
w = MyApplication()
w.show()
sys.exit(app.exec_())
You don't bind an actual function call using PySide signals/slots, you bind a function, method, or function-like object using PySide's signals/slots.
You have:
self.fileButton.clicked.connect(self.select_file(self.fileButton))
This tells Qt to bind the click event to something that is returned from the self.select_file function call, which presumably has no __call__ attribute and is called immediately (causing the opening of the QFileDialog)
What you want is the following:
from functools import partial
self.fileButton.clicked.connect(partial(self.select_file, self.fileButton))
This creates a callable, frozen function-like object with arguments for Qt to call.
This is comparable to saying:
self.fileButton.clicked.connect(self.select_file)
Rather than saying:
self.fileButton.clicked.connect(self.select_file())

PyQt: Accesing Main Window's Data from a dialog?

So, I'm using Python and PyQt. I have a Main Window that contains a QTableWidget, and a dialog that opens modally and has some QLineEdit widgets... All right so far, but I have 2 problems:
When the dialog opens, my Main Window freezes, and I don't really like that...
What I want, when I finish editing a QLineEdit, is that the program will search the QTableWidget, and if the text from the QLineEdit exists in the table, a dialog will come up and informe about that. That's the general idea. But, so far, I seem to only be able to create a new QTableWidget instance, and I can't use the data from the existing...
What can I do about these?
You wrote:
and a dialog that opens modally
and then:
When the dialog opens, my Main Window freezes
The docs say:
int QDialog::exec () [slot]
Shows the dialog as a modal dialog,
blocking until the user closes it. The function returns a DialogCode
result. If the dialog is application modal, users cannot interact with
any other window in the same application until they close the dialog.
If the dialog is window modal, only interaction with the parent window
is blocked while the dialog is open. By default, the dialog is
application modal.
About modeless dialogs:
A modeless dialog is a dialog that operates independently of other
windows in the same application. Find and replace dialogs in
word-processors are often modeless to allow the user to interact with
both the application's main window and with the dialog.
Modeless
dialogs are displayed using show(), which returns control to the
caller immediately.
An example:
import sys
from PyQt4 import QtCore, QtGui
class SearchDialog(QtGui.QDialog):
def __init__(self, parent = None):
QtGui.QDialog.__init__(self, parent)
self.setWindowTitle('Search')
self.searchEdit = QtGui.QLineEdit()
layout = QtGui.QVBoxLayout()
layout.addWidget(self.searchEdit)
self.setLayout(layout)
class MainWindow(QtGui.QDialog):
def __init__(self):
QtGui.QDialog.__init__(self, None)
self.resize(QtCore.QSize(320, 240))
self.setWindowTitle('Main window')
self.logText = QtGui.QPlainTextEdit()
searchButton = QtGui.QPushButton('Search')
layout = QtGui.QVBoxLayout()
layout.addWidget(self.logText)
layout.addWidget(searchButton)
self.setLayout(layout)
searchButton.clicked.connect(self.showSearchDialog)
def showSearchDialog(self):
searchDialog = SearchDialog(self)
searchDialog.show()
searchDialog.searchEdit.returnPressed.connect(self.onSearch)
def onSearch(self):
self.logText.appendPlainText(self.sender().text())
def main():
app = QtGui.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
app.exec_()
if __name__ == "__main__":
main()
Click 'Search' to open a search window (you can open several of them). Enter a text to search and press Enter. The text to search will be added to the log in the main window.

Displaying pop-up windows in Python (PyQt4)

I need to know how to be able to make a dialog pop-up when a user clicks a button.
I'm relatively new to both Python and PyQt/QtDesigner. I've only been using in them for about a month, but I think I have a good grasp.
Here's what I have: A main dialog (which is the main part of the application), which I designed in QtDesigner. I converted the .ui to .py using pyuic4easy.
Here's what I want to do: design a new dialog box in the QtDesigner and somehow make it pop up when a user clicks a button on the first (main) dialog.
Here's the code for my main dialog:
import sys
from PyQt4.QtCore import *
from loginScreen import *
class MyForm(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
QtCore.QObject.connect(self.ui.pushButton, QtCore.SIGNAL('clicked()'), self.popup)
...
... Some functions ...
def popup(self):
#Pop-up the new dialog
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp= MyForm()
myapp.show()
sys.exit(app.exec_())
So as you can see, I've connected the first button to a method named 'popup', which needs to be filled in with code to make my second window pop up. How do I go about doing this? Remember that I already have designed my second dialog in QtDesigner, and I don't need to create a new one.
Thanks for all the help!
So as you can see, I've connected the first button to a method named
'popup', which needs to be filled in with code to make my second
window pop up. How do I go about doing this?
Pretty much the same way you do it for your main window (MyForm).
As usual, you write a wrapper class for your QtDesigner code for the second dialog (like you did with MyForm). Let's call it MyPopupDialog. Then in your popup method, you create an instance and then show your instance with either exec_() or show() depending whether you want a modal or modeless dialog. (If you are not familiar with Modal/Modeless concept, you might refer to the documentation.)
So the overall thing might look like this (with a couple of modifications):
# Necessary imports
class MyPopupDialog(QtGui.QDialog):
def __init__(self, parent=None):
# Regular init stuff...
# and other things you might want
class MyForm(QtGui.QDialog):
def __init__(self, parent=None):
# Here, you should call the inherited class' init, which is QDialog
QtGui.QDialog.__init__(self, parent)
# Usual setup stuff
self.ui = Ui_Dialog()
self.ui.setupUi(self)
# Use new style signal/slots
self.ui.pushButton.clicked.connect(self.popup)
# Other things...
def popup(self):
self.dialog = MyPopupDialog()
# For Modal dialogs
self.dialog.exec_()
# Or for modeless dialogs
# self.dialog.show()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp= MyForm()
myapp.show()
sys.exit(app.exec_())

Categories