Proper way to quit/exit a PyQt program - python

I have a script which has a login screen and if the cancel button is pressed, I want to exit the application altogether. I have tried 3 ways:
sys.exit()
QApplication.quit()
QCoreApplication.instance().quit()
Only number 1 works. The other two makes the dialog box white and it flashes then hangs and I cannot even switch to other applications. My code is below:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtSql import *
from PyQt5.QtWidgets import *
import csv
import sys
from datetime import datetime, timedelta, time
import os
from ci_co_table import *
from login import *
class Ci_Co(QMainWindow):
"""Check in and check out module"""
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
class Login(QDialog):
"""User login """
def __init__(self):
QDialog.__init__(self)
self.ui = Ui_login_form()
self.ui.setupUi(self)
self.ui.buttonBox.accepted.connect(lambda: self.handle_login(servers=servers))
servers = {}
with open('servers.csv', newline='') as csvfile:
server_reader = csv.reader(csvfile)
for row in server_reader:
self.ui.cbo_db_name.addItem(row[1])
servers[row[1]] = (row[0],row[2])
def handle_login(self, servers=''):
global user
global pword
global database
global server
global bg_colour
user = self.ui.username.text()
pword = self.ui.password.text()
database = self.ui.cbo_db_name.currentText()
server = servers[database][0]
bg_colour = servers[database][1]
if __name__=="__main__":
app=QApplication(sys.argv)
global hotdate
global hotdate_string
global folio_num
global user
global pword
global dbase
global server
pword = ""
global database
global bg_colour
#Login
while True:
if Login().exec_() == QDialog.Accepted:
db = QSqlDatabase.addDatabase("QPSQL");
db.setHostName(server)
db.setDatabaseName(database);
db.setUserName(user);
db.setPassword(pword)
if (db.open()==False):
QMessageBox.critical(None, "Database Error", db.lastError().text())
else:
break
else:
#QApplication.quit()
QCoreApplication.instance().quit()
#sys.exit()
myapp = Ci_Co()
myapp.show()
sys.exit(app.exec_())

Calling QCoreApplication.quit() is the same as calling QCoreApplication.exit(0). To quote from the qt docs:
After this function has been called, the application leaves the main
event loop and returns from the call to exec(). The exec() function
returns returnCode. If the event loop is not running, this function
does nothing. [emphasis added]
So quit() or exit() are nothing like sys.exit(). The latter will terminate the program, but the former will merely terminate the event-loop (if it's running).
When the user cancels the login dialog, your example should just call sys.exit() to terminate the program. Otherwise, your program will just get stuck in the blocking while-loop.

Instead of using QApplication.quit(), since you defined app = QApplication(sys.argv), you could just write app.quit(), and that should work!
Something unrelated but might be helpful: I think it would be easier if you put the login check at the beginning of the __init__ function of your Ci_Co class. That way, you will start Ci_Co at the beginning, but it will first spawn the Login class. If the login fails, you can call app.quit(), and if it succeeds, it will automatically transition into Ci_Co. This saves you from a lot of the things you have to write in the if __name__ == "__main__" clause. Please comment if you have any more questions, I have a similar project with a login dialog box.

i try this 3 method to close my MainWindow() but it didn't work for my code.
sys.exit()
QApplication.quit()
qApp.quite()
So i use self.close() method which work Completely fine with my code.
here is my code
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.QPushButton.clicked.connect(lambda: self.shutprocess())
def shutprocess(self):
reply = QMessageBox.question(self, 'Window Close', 'Are you sure you want to close the window?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
self.close()
print('Window closed')
else:
pass

add sys.exit(app.exec_()) To youer Code

Related

Qt wait for window to close

I am writing a Python thing and I need the code to wait for the Qt to finish before continuing.
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
class Test():
def __init__(self):
self.open_qt()
def open_qt(self):
app = QtWidgets.QApplication(sys.argv)
self.window = QtWidgets.QWidget() # I tried QDialog also
btn = QtWidgets.QPushButton("press me")
btn.clicked.connect(self.login)
lay = QtWidgets.QVBoxLayout()
lay.addWidget(btn)
self.window.setLayout(lay)
self.window.show()
sys.exit(app.exec_())
def login(self):
print("logged in!")
print("before")
temp = Test()
print("after")
This prints as:
before
after
logged in!
or:
before
logged in!
(after never arrives even after closing the Qt window)
But I need it to be:
before
logged in!
after
Okey so the problem in your case was with the sys.exit(app.exec_()) at the end of the open_qt(self) method:
This line of code sys.exit(app.exec_()) means that after app closes (executes), you will also immediately call the sys.exit() function.
sys.exit() then immediately terminates the script so your code doesn't get to the print("after") statement.
Rewrite this part to just simply: app.exec_()
Then you will see the after statement print out without any problem after you close the GUI.

Opening another window after successful login PyQt5 Python3

I want to open another window upon a successful login but it continuously give this error.
QCoreApplication::exec: The event loop is already running
Here is the code.
LoginWindow - Login Page
MainWindow - Post login page
Is there anyway I can open another window upon a successful login and close the current one.
from PyQt5.QtWidgets import QMainWindow, QApplication
from login_ui import *
import sys
LOGGED_IN = False
TOKEN = ""
class LoginWindow(QMainWindow, Ui_LoginWindow):
GLOBAL_STATE = True
loginPortal = QApplication([])
def __init__(self):
super().__init__()
self.setupUi(self)
self.setWindowFlag(Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.show()
self.handleButtons()
sys.exit(self.loginPortal.exec_())
def handleButtons(self):
self.CloseBtn.clicked.connect(self.close)
self.MinBtn.clicked.connect(self.showMinimized)
self.loginBtn.clicked.connect(self.login)
def login(self):
if self.username.text() == "admin" and self.password.text() == "password":
LOGGED_IN = True
TOKEN = "admin"
elif self.username.text() == "coord" and self.password.text() == "password":
LOGGED_IN = True
TOKEN = "coord"
else:
LOGGED_IN = False
Token = None
if LOGGED_IN:
MainWindow().show()
else:
self.close()
self.loginPortal.quit()
from main_ui import *
class MainWindow(QMainWindow, Ui_MainWindow):
app = QApplication([])
def __init__(self):
super().__init__()
self.setupUi(self)
self.setWindowFlag(Qt.FramelessWindowHint)
self.showMaximized()
self.handleButtons()
sys.exit(self.app.exec_())
def handleButtons(self):
self.MinBtn.clicked.connect(self.showMinimized)
self.MaxBtn.clicked.connect(self.maximize)
self.CloseBtn.clicked.connect(self.close)
def maximize(self):
if self.GLOBAL_STATE:
self.showNormal()
self.GLOBAL_STATE = False
else:
self.showMaximized()
self.GLOBAL_STATE = True
if __name__ == "__main__":
LoginWindow()
There are two problems with your code:
Only one QApplication instance should exist.
You're creating a QApplication in the class definition.
The second problem is important, as QtWidgets can only be created when a QApplication is created. When python opens a script, it also initializes all the classes in its main indentation, which also means that every function in the base indentation of the class is executed.
In your case, it means that when each class is loaded, each one will try to create a QApplication, since app = QApplication([]) is in their main indentation. As explained in the first point, only one QApplication instance can only exist.
While technically your code could run fine when using a single class (the QApplication is created before actually creating the widget instance), this leads to problems exactly like in your case.
A possible workaround would be to check if a QApplication already exists:
# note: this is just for explanation purposes, do NOT do this unless you
# really know what you're doing
class LoginWindow(QMainWindow, Ui_MainWindow):
if not QApplication.instance():
loginPortal = QApplication([])
# ...
class MainWindow(QMainWindow, Ui_MainWindow):
if not QApplication.instance():
app = QApplication([])
But that's not a good approach anyway, and the reason is that the application should be also executed with exec_() (or exec()). While you're doing it within the __init__ of each widget, that's absolutely not the place for that: exec_() is blocking, which prevents returning the actual instance to the caller until the application quits. Even if you're not actually interested in that, it's not good practice, as the __init__ should always return as soon as possible. Finally, when you try to create the main window instance, it will try to exec the other QApplication instance, and this is the (final) source of your problem.
That's what the common if __name__ == '__main__': is important for: ensuring that only the main program executes what should be actually run, and in case of Qt, to create and run the only QApplication that should ever exist in the program lifetime.
A better approach when dealing with multiple windows is to create a signal and use that to interact with anything else.
This is a possible correct version of your code (be aware of the commented lines)
class LoginWindow(QMainWindow, Ui_LoginWindow):
loggedSignal = pyqtSignal()
GLOBAL_STATE = True
# loginPortal = QApplication([])
def __init__(self):
super().__init__()
self.setupUi(self)
self.setWindowFlag(Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.show()
self.handleButtons()
# sys.exit(self.loginPortal.exec_())
# ...
def login(self):
# ...
if LOGGED_IN:
self.loggedSignal.emit()
else:
QApplication.quit()
self.close()
class MainWindow(QMainWindow, Ui_MainWindow):
# app = QApplication([])
def __init__(self):
super().__init__()
self.setupUi(self)
self.setWindowFlag(Qt.FramelessWindowHint)
# self.showMaximized()
self.handleButtons()
# sys.exit(self.app.exec_())
if __name__ == '__main__':
app = QApplication(sys.argv)
login = LoginWindow()
mainWindow = MainWindow()
login.loggedSignal.connect(mainWindow.showMaximized)
sys.exit(app.exec_())
Note that you are using the GLOBAL_STATE in the main window, but you defined it in the login window only instead.

Getting closeEvent when exiting the application

I'm trying to make a small python programs which is able to have several windows. The issue is when I try to implement a menu entry to quit the programs, closing all the windows at once. I've tried to use qApp.close() and qApp.exit() but if those allow to effectively quit the program, there is no close events generated for the windows still opened, which prevent me to save modified data or to prevent leaving the application. What's the best practice for that? I could understand not being able to cancel the exit process, but being able to propose to save modified data is something I really want.
import sys
from PyQt5.QtWidgets import *
opened_windows = set()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.create_actions()
opened_windows.add(self)
def closeEvent(self, ev):
if QMessageBox.question(self, 'Closing', 'Really close?') == QMessageBox.Yes:
ev.accept()
opened_windows.remove(self)
else:
ev.ignore()
def create_action(self, action_callback, menu, action_name):
action = QAction(action_name, self)
action.triggered.connect(action_callback)
menu.addAction(action)
def create_actions(self):
_file_menu = self.menuBar().addMenu('&File')
self.create_action(self.on_new, _file_menu, '&New')
_file_menu.addSeparator()
self.create_action(self.on_close, _file_menu, '&Close')
self.create_action(self.on_quit, _file_menu, '&Quit')
self.create_action(self.on_exit, _file_menu, '&Exit')
def on_new(self):
win = MainWindow()
win.show()
def on_close(self):
self.close()
def on_quit(self):
qApp.quit()
def on_exit(self):
qApp.exit(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
win.show()
status = app.exec()
print(len(opened_windows), ' window(s) opened')
print('status = ', status)
sys.exit(status)
Currently I'm modifying on_close and on_exit like this:
def on_exit(self):
for w in opened_windows.copy():
w.on_close()
if len(opened_windows) == 0:
qApp.exit(1)
but I wonder if I'm missing a better way which would not force me to maintain a set of opened windows.
Cause
It is important to understand, that the app and the main window are related, but are not the same thing. So, when you want to close the program, don't bother closing the app. Close the main window instead. From the documentation of QCloseEvent :
Close events are sent to widgets that the user wants to close, usually by choosing "Close" from the window menu, or by clicking the X title bar button. They are also sent when you call QWidget::close() to close a widget programmatically.
Solution
Connect your exit-action's triggered signal to the close slot of your MainWindow. In your case, instead of:
self.create_action(self.on_exit, _file_menu, '&Exit')
write:
self.create_action(self.close, _file_menu, '&Exit').
Define in MainWindow a signal closed and emit it from your implementation of the closedEvent, e.g. in the place of opened_windows.remove(self)
In on_new connect win.closed to self.close
Example
Here is how I suggest you to change your code in order to implement the proposed solution:
import sys
from PyQt5.QtWidgets import *
class MainWindow(QMainWindow):
closed = pyqtSignal()
def __init__(self):
super().__init__()
self.create_actions()
def closeEvent(self, ev):
if QMessageBox.question(self, 'Closing', 'Really close?') == QMessageBox.Yes:
ev.accept()
self.closed.emit()
else:
ev.ignore()
def create_action(self, action_callback, menu, action_name):
action = QAction(action_name, self)
action.triggered.connect(action_callback)
menu.addAction(action)
def create_actions(self):
_file_menu = self.menuBar().addMenu('&File')
self.create_action(self.on_new, _file_menu, '&New')
_file_menu.addSeparator()
self.create_action(self.close, _file_menu, '&Exit')
def on_new(self):
win = MainWindow()
win.show()
win.closed.connect(self.close)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
win.show()
status = app.exec()
print('status = ', status)
sys.exit(status)
Edit: I wonder how I missed it before. There is the QApplication::closeAllWindows slot which does exactly what I want and whose example is a binding to exit.
There is a way to propose to save modified data on quit and exit, the signal QCoreApplication::aboutToQuit.
Note that although the Qt documentation says that user interaction is not possible, at least with PyQt5 I could use a QMessageBox without apparent issues.

PySide/PyQt: Execute functions after GUI loads

I am building a tiny tool that does file operations at session startup. In order to make sure the user has a visual feedback, I want to associate it with a progress bar.
Here I am so far:
import sys
import time
from PySide.QtGui import *
class ProgressWindowWidget(QWidget):
def __init__(self, parent=None):
super(ProgressWindowWidget, self).__init__()
self.init_ui()
def init_ui(self):
self.setGeometry(500, 500, 600, 100)
self.setWindowTitle('Progress')
self.layout_ = QGridLayout()
self.setLayout(self.layout_)
self.progress_bar = QProgressBar()
self.layout_.addWidget(self.progress_bar, 0, 0, 1, 1)
def my_operations(self):
print('do something 1')
time.sleep(2)
print('do something 2')
time.sleep(2)
print('do something 3')
time.sleep(2)
def main():
app = QApplication(sys.argv)
progress_window = ProgressWindowWidget()
progress_window.show()
progress_window.my_operations()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
My problem is that my_operations is executed first and then my GUI is loaded. I'd like to execute my_operations only when the progress bar is loaded, so I can update it.
According to this, it has something to do with the exec_ main loop, but there's obviously something I don't understand here because I am calling my_operations after show.
Needless to say, I'm a beginner. Does anyone have an idea?
Cheers
Every GUI lives in an event loop that allows you to handle events of the user, the OS, etc. such as the mouse, the keyboard, etc., so if you block this processing the GUI will not update its status, in your case the problem is caused by time.sleep(), which is blocking, preventing the GUI from activating the state of displaying the window. So as a basic rule: do not use time.sleep() inside the main thread of a GUI, I suppose the time.sleep() emulates a task that takes a certain time, for that case you must execute this task from another thread and if you want update the GUI you must do it by means of signals, it should not be done directly.
In the following example I will use threading.Thread() to create a new thread and a signal to update the GUI:
import sys
import time
import threading
from PySide import QtCore, QtGui
class ProgressWindowWidget(QtGui.QWidget):
progressSignal = QtCore.Signal(int)
def __init__(self, parent=None):
super(ProgressWindowWidget, self).__init__()
self.init_ui()
def init_ui(self):
self.setGeometry(500, 500, 600, 100)
self.setWindowTitle('Progress')
self.layout_ = QtGui.QGridLayout()
self.setLayout(self.layout_)
self.progress_bar = QtGui.QProgressBar()
self.progressSignal.connect(self.progress_bar.setValue)
self.layout_.addWidget(self.progress_bar, 0, 0, 1, 1)
def my_operations(self):
print('do something 1')
time.sleep(2)
self.progressSignal.emit(33)
print('do something 2')
time.sleep(2)
self.progressSignal.emit(66)
print('do something 3')
time.sleep(2)
self.progressSignal.emit(100)
def main():
app = QtGui.QApplication(sys.argv)
progress_window = ProgressWindowWidget()
progress_window.show()
t = threading.Thread(target=progress_window.my_operations)
t.start()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

PyQt5 Interrupt Close Event from Outside the Main Gui Module

I use Qt Designer to build my GUI's and convert them to py files using pyuic5. My end goal here is to interrupt the user from closing the program when a variable == 1 and present them with an 'are you sure you want to close?' type dialog. If said variable == 0 then just close the program normally.
I have seen lots of examples on how to do this, but all of them require editing the code in the GUI module. I import my gui.py file created by pyuic5 into my main script where I do all my connections to buttons, line edits, etc.. I do this so that at anytime I can update the GUI with Qt Designer and not affect the programs functionality.
Is there a way to do this from my main script that has the GUI module from Qt Designer imported?
Example of how my main script is structured:
import philipsControlGui
import sys
def main():
MainWindow.show()
sys.exit(app.exec_())
def test():
print('test')
# Main window setup
app = philipsControlGui.QtWidgets.QApplication(sys.argv)
MainWindow = philipsControlGui.QtWidgets.QMainWindow()
ui = philipsControlGui.Ui_MainWindow()
ui.setupUi(MainWindow)
# Main window bindings
ui.onButton.clicked.connect(test)
### Can I insert something here to do: if user closes the window... do something else instead?
if __name__ == "__main__":
main()
You should create a subclass from your imported gui so you can reimplement the closeEvent method:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from philipsControlGui import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setUpUi(self)
self.ui.onButton.clicked.connect(self.test)
self._check_close = True
def test(self):
print('test')
self._check_close = not self._check_close
def closeEvent(self, event):
if self._check_close:
result = QtWidgets.QMessageBox.question(
self, 'Confirm Close', 'Are you sure you want to close?',
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if result == QtWidgets.QMessageBox.Yes:
event.accept()
else:
event.ignore()
def main():
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
If there's a specific 'ExitButton' in your design, you should be able to connect it in the main code and create a pop up dialog. You would have to import the QtCore/QtGui components. I always write my GUI directly (QtDesigner is pain when it comes to these things) so I'm assuming something like this:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
[YOUR CODE]
ui.ExitButton.clicked.connect(Exit)
def Exit():
msg = QMessageBox()
msg.setIcon(QMessageBox.Information)
msg.setText("Are you sure you want to close this window?")
msg.setWindowTitle("MessageBox demo")
msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
msg.buttonClicked.connect(msgbtn)
retval = msg.exec_()
print "value of pressed message box button:", retval

Categories

Resources