PyQt5 closeEvent method - python

I'm currently learning how to build an application with pyqt5 and encountered some problem with closeEvent method, overriden so user gets asked for confirmation by QMessageBox object. It seems working well with X button - event gets 'accepted' when action is confirmed and 'canceled' when cancel button is clicked. However, when I use my Quit button from dropdown File menu, no matter which button I click, program gets closed with exit code 1. Seems strange, because I use same closeEvent method in both cases.
import sys
from PyQt5.QtWidgets import QApplication, QMessageBox, QMainWindow, QAction
class window(QMainWindow):
def __init__(self):
super().__init__()
def createUI(self):
self.setGeometry(500, 300, 700, 700)
self.setWindowTitle("window")
quit = QAction("Quit", self)
quit.triggered.connect(self.closeEvent)
menubar = self.menuBar()
fmenu = menubar.addMenu("File")
fmenu.addAction(quit)
def closeEvent(self, event):
close = QMessageBox()
close.setText("You sure?")
close.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel)
close = close.exec()
if close == QMessageBox.Yes:
event.accept()
else:
event.ignore()
main = QApplication(sys.argv)
window = window()
window.createUI()
window.show()
sys.exit(main.exec_())
Thanks for suggestions!

When you click button then program calls your function but with different event object which doesn't have accept() and ignore() so you get error message and program ends with exit code 1.
You can assign self.close and program will call closeEvent() with correct event object.
quit.triggered.connect(self.close)

The problem is accept is a method while ignore is just an attribute.
This code works for me:
def closeEvent(self, event):
close = QtWidgets.QMessageBox.question(self,
"QUIT",
"Are you sure want to stop process?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if close == QtWidgets.QMessageBox.Yes:
event.accept()
else:
event.ignore()

I had the same problem and fixed with a type-check-hack. It might be an ugly hack, but it works (tested on macOS 10.15 with python 3.8.0 and PyQt 5.14.2).
class Gui(QtWidgets.QMainWindow):
def __init__(self):
super(Gui, self).__init__()
uic.loadUi("gui.ui", self)
# ...
self.actionExit = self.findChild(QtWidgets.QAction, "actionExit")
self.actionExit.triggered.connect(self.closeEvent)
# ...
def closeEvent(self, event):
reply = QMessageBox.question(self, 'Quit?',
'Are you sure you want to quit?',
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
if not type(event) == bool:
event.accept()
else:
sys.exit()
else:
if not type(event) == bool:
event.ignore()

If you want to close an PyQt5 app from a menu:
When menu event triggered call: self.MainWindow.close() (or what window do you want to close
Add this code before sys.exit(app.exec()): self.MainWindow.closeEvent = lambda event:self.closeEvent(event)
Declare def closeEvent(self,event): method when you really want to close call event.accept() (and perhaps return 1) and if you don't want to close the window call event.ignore() (not event.reject() (it's not working for me))

def exit_window(self, event):
close = QtWidgets.QMessageBox.question(self,
"QUIT?",
"Are you sure want to STOP and EXIT?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if close == QtWidgets.QMessageBox.Yes:
# event.accept()
sys.exit()
else:
pass

Related

Prevent PySide2 dialog from closing when QRunnable still running

I have a PySide2 application where I'm executing a long running process using QRunnable and I don't want the user to accidentally close the dialog until the finished signals is emitted.
While I can use self.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False) and re-enable it after the QRunnable finished running, I prefer to have a way alert the user that the function is still running if they accidentally close it (despite the dialog showing a progress bar and output log).
I'm thinking of subclassing and override the closeEvent but I wonder if there is other or even better way to approach this problem.
Here's a working example of a closeEvent override that satisfy the need of my original question.
import sys
from PySide2.QtGui import *
from PySide2.QtWidgets import *
from PySide2.QtCore import *
class CloseEventOverrideDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowFlag(Qt.WindowMinimizeButtonHint, True)
self.setWindowFlag(Qt.WindowMaximizeButtonHint, True)
self.setWindowTitle("closeEvent Override Example")
self.is_busy = False
self.setup_ui()
def closeEvent(self, event: QCloseEvent):
print(f"{self.is_busy=}")
if self.is_busy:
msg = (
f"Something is busy running. Do you wish to abort the progress?"
)
is_cancel = QMessageBox.warning(
self,
"Warning!",
msg,
QMessageBox.Ok,
QMessageBox.Cancel
)
print(f"{is_cancel=}")
if is_cancel == QMessageBox.Cancel:
# Prevent dialog from closing!
event.ignore()
else:
print(f"User explicitly close dialog!")
event.accept()
def setup_ui(self):
self.layout = QVBoxLayout()
self.setLayout(self.layout)
checked_label = QLabel(
"Checked the following checkbox to trigger a QMessageBox Warning dialog "
"when user close the dialog."
)
self.layout.addWidget(checked_label)
self.busy_checkbox = QCheckBox("Busy")
self.busy_checkbox.stateChanged.connect(self.toggle_is_busy)
self.layout.addWidget(self.busy_checkbox)
def toggle_is_busy(self):
self.is_busy = True if self.busy_checkbox.isChecked() else False
print(f"{self.is_busy=}")
def main():
app = QApplication(sys.argv)
dialog = CloseEventOverrideDialog()
dialog.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Screenshot of QMessageBox Warning when user tries to close the dialog with closeEvent override

PyQt - Showing a MessageBox when clicking a 'Quit' button

I have an application which inherits form QtGui.QMainWindow and which redefines the closeEvent to show a MessageBox.
def closeEvent(self, event):
reply = QtGui.QMessageBox.question(
self,
'Quit',
'Are you sure you want to quit?',
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
QtGui.QMessageBox.Yes)
if reply == QtGui.QMessageBox.Yes:
event.accept()
else:
event.ignore()
This MessageBox shows up when I click on the 'X' in the window. The Application also has a 'Quit' button. I tried to connect the button to the redefinition of the closeEvent, so when I click the button the MessageBox shows up. But when I confirm that I want to quit, I just get back to to my Application.
def create_components(self):
self.button = QtGui.QPushButton('Quit')
self.button.clicked.connect(self.button_quit)
def button_quit(self):
self.status_bar.showMessage('Leaving Application')
# QtCore.QCoreApplication.instance().quit()
self.closeEvent(QtGui.QCloseEvent())
The 'create_components' method is called in the init.
Call self.close() and closeEvent will be emitted by Qt
def button_quit(self):
self.status_bar.showMessage('Leaving Application')
self.close()

pyqt5 - closing/terminating application

I'm working though the pyqt5 tutorial found here Zetcode, PyQt5
As an exercise for myself I'm trying to expand on an example so that I am presented with the same dialog message box regardless of method used to close the app:
clicking the 'X' button in the title bar (works as intended)
clicking the 'Close' button (produces attribute error)
pressing the 'escape' key (works but not sure how/why)
The dialog message box is implemented in the closeEvent method, full script provided at the end.
I'm having two issues:
1. When clicking 'Close' button, instead of just quitting, I want to call closeEvent method including message box dialog.
I have replaced a line of the example code for the 'Close' push button:
btn.clicked.connect(QCoreApplication.instance().quit)
And instead am trying to call the closeEvent method which already implements the dialog I want:
btn.clicked.connect(self.closeEvent)
However when i run the script and click the 'Close' button and select the resulting 'Close' option in the dialog i get the following:
Traceback (most recent call last):
File "5-terminator.py", line 41, in closeEvent
event.accept()
AttributeError: 'bool' object has no attribute 'accept'
Aborted
Can anyone advise what I'm doing wrong and what needs to be done here?
2. When hitting the escape key somehow the message box dialog is presented and works just fine.
Ok, it's great that it works, but I'd like to know how and why the message box functionality defined in CloseEvent method is called within the keyPressEvent method.
Full script follows:
import sys
from PyQt5.QtWidgets import (
QApplication, QWidget, QToolTip, QPushButton, QMessageBox)
from PyQt5.QtCore import QCoreApplication, Qt
class Window(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
btn = QPushButton("Close", self)
btn.setToolTip("Close Application")
# btn.clicked.connect(QCoreApplication.instance().quit)
# instead of above button signal, try to call closeEvent method below
btn.clicked.connect(self.closeEvent)
btn.resize(btn.sizeHint())
btn.move(410, 118)
self.setGeometry(30, 450, 500, 150)
self.setWindowTitle("Terminator")
self.show()
def closeEvent(self, event):
"""Generate 'question' dialog on clicking 'X' button in title bar.
Reimplement the closeEvent() event handler to include a 'Question'
dialog with options on how to proceed - Save, Close, Cancel buttons
"""
reply = QMessageBox.question(
self, "Message",
"Are you sure you want to quit? Any unsaved work will be lost.",
QMessageBox.Save | QMessageBox.Close | QMessageBox.Cancel,
QMessageBox.Save)
if reply == QMessageBox.Close:
event.accept()
else:
event.ignore()
def keyPressEvent(self, event):
"""Close application from escape key.
results in QMessageBox dialog from closeEvent, good but how/why?
"""
if event.key() == Qt.Key_Escape:
self.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
sys.exit(app.exec_())
Hope someone can take the time to enlighten me.
Your second question answers the first question.
The reimplemented keyPressEvent method calls close(), which sends a QCloseEvent to the widget. Subsequently, the widget's closeEvent will be called with that event as its argument.
So you just need to connect the button to the widget's close() slot, and everything will work as expected:
btn.clicked.connect(self.close)
Unlike the X button your custom button does not seem to pass an close event just a bool. That's why this exercise should work for the X button but not a normal button. In any case, for your first question you might use destroy() and pass instead (of accept and ignore) just like this:
import sys
from PyQt5.QtWidgets import (
QApplication, QWidget, QToolTip, QPushButton, QMessageBox)
from PyQt5.QtCore import QCoreApplication, Qt
class Window(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
btn = QPushButton("Close", self)
btn.setToolTip("Close Application")
# btn.clicked.connect(QCoreApplication.instance().quit)
# instead of above button signal, try to call closeEvent method below
btn.clicked.connect(self.closeEvent)
btn.resize(btn.sizeHint())
btn.move(410, 118)
self.setGeometry(30, 450, 500, 150)
self.setWindowTitle("Terminator")
self.show()
def closeEvent(self, event):
"""Generate 'question' dialog on clicking 'X' button in title bar.
Reimplement the closeEvent() event handler to include a 'Question'
dialog with options on how to proceed - Save, Close, Cancel buttons
"""
reply = QMessageBox.question(
self, "Message",
"Are you sure you want to quit? Any unsaved work will be lost.",
QMessageBox.Save | QMessageBox.Close | QMessageBox.Cancel,
QMessageBox.Save)
if reply == QMessageBox.Close:
app.quit()
else:
pass
def keyPressEvent(self, event):
"""Close application from escape key.
results in QMessageBox dialog from closeEvent, good but how/why?
"""
if event.key() == Qt.Key_Escape:
self.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
sys.exit(app.exec_())
For your second question Qt has default behaviors depending on the Widget (Dialogs might have another, try pressing the Esc key when your Message Dialog is open just to see). When you do need to override the Esc behavior you might try this:
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Escape:
print("esc")
As you'll eventually see in ZetCode.
closeEvent()
In above right tick mark code please check the closeEvent it is same other wise it's waste of time to research.

PyQt: application unexpectedly leaves main loop

I have a simple PyQt application, which has a tray icon and can be hidden from taskbar by clicking close button or tray icon. Application can be closed from tray icon context menu. After user clicks "exit" in context menu, modal window with confirmation question appears . If user clicks "yes" application closes, if "no" application continues working.
When main window is hidden, application will be closed, even if user click "No" in modal window, but everything correct when window isn't hidden. This also happens with any modal window, for example with some information. I suppose the are some "magic" in parent paarmeter for QtGui.QMessageBox.question, but I don't know how to handle it. Please help to fix this annoying bug.
Here is the code:
import sys
from datetime import datetime
from PyQt4 import QtGui, QtCore
class SampleWindow(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.init_ui()
self.tray_icon.activated.connect(self.tray_click)
self.show_window.triggered.connect(self.show_from_tray)
self.now_button.triggered.connect(self.info)
self.appexit.triggered.connect(self.app_close)
def init_ui(self):
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Message box')
self.tray_icon = QtGui.QSystemTrayIcon()
self.tray_icon.setIcon(QtGui.QIcon('clock.ico'))
self.tray_icon.show()
self.iconMenu = QtGui.QMenu()
self.show_window = self.iconMenu.addAction("MyApp")
self.show_window.setDisabled(True)
self.iconMenu.addSeparator()
self.now_button = self.iconMenu.addAction("Now")
self.appexit = self.iconMenu.addAction("Exit")
self.tray_icon.setContextMenu(self.iconMenu)
def info(self):
now = str(datetime.now())
QtGui.QMessageBox.information(self, 'Now', now)
def app_close(self):
info_msg = "Are you sure to quit?"
reply = QtGui.QMessageBox.question(self,
'Exit',
info_msg,
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
QtGui.QApplication.quit()
def closeEvent(self, event):
self.hide_to_tray()
event.ignore()
def show_from_tray(self):
self.setWindowFlags(QtCore.Qt.Window)
self.showNormal()
self.activateWindow()
self.show_window.setDisabled(True)
def hide_to_tray(self):
self.setWindowFlags(QtCore.Qt.Tool)
self.show_window.setDisabled(False)
def tray_click(self, reason):
if reason != QtGui.QSystemTrayIcon.Context:
if self.isHidden():
self.show_from_tray()
else:
self.hide_to_tray()
def main():
app = QtGui.QApplication(sys.argv)
sample = SampleWindow()
sample.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
By default Qt application is terminated when the last window is closed. I'm not sure why it is closed after invoking context menu. I think it's because you hide your window in a strange way. Usually a window can be hidden using hide().
This behavior can be easily disabled:
app = QtGui.QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)

PyQt4 Minimize to Tray

Is there a way to minimize to tray in PyQt4? I've already worked with the QSystemTrayIcon class, but now I would like to minimize or "hide" my app window, and show only the tray icon.
Has anybody done this? Any direction would be appreciated.
Using Python 2.5.4 and PyQt4 on Window XP Pro
It's pretty straightforward once you remember that there's no way to actually minimize to the system tray.
Instead, you fake it by doing this:
Catch the minimize event on your window
In the minimize event handler, create and show a QSystemTrayIcon
Also in the minimize event handler, call hide() or setVisible(false) on your window
Catch a click/double-click/menu item on your system tray icon
In your system tray icon event handler, call show() or setVisible(true) on your window, and optionally hide your tray icon.
Code helps, so here's something I wrote for an application, except for the closeEvent instead of the minimize event.
Notes:
"closeEvent(event)" is an overridden Qt event, so it must be put in the class that implements the window you want to hide.
"okayToClose()" is a function you might consider implementing (or a boolean flag you might want to store) since sometimes you actually want to exit the application instead of minimizing to systray.
There is also an example of how to show() your window again.
def __init__(self):
traySignal = "activated(QSystemTrayIcon::ActivationReason)"
QtCore.QObject.connect(self.trayIcon, QtCore.SIGNAL(traySignal), self.__icon_activated)
def closeEvent(self, event):
if self.okayToClose():
#user asked for exit
self.trayIcon.hide()
event.accept()
else:
#"minimize"
self.hide()
self.trayIcon.show() #thanks #mojo
event.ignore()
def __icon_activated(self, reason):
if reason == QtGui.QSystemTrayIcon.DoubleClick:
self.show()
Just to add to the example by Chris:
It is crucial that you use the Qt notation when declaring the signal, i.e.
correct:
self.connect(self.icon, SIGNAL("activated(QSystemTrayIcon::ActivationReason)"), self.iconClicked)
and not the PyQt one
incorrect and won't work:
self.connect(self.icon, SIGNAL("activated(QSystemTrayIcon.ActivationReason)"), self.iconClicked)
Note the :: in the signal string. This took me about three hours to figure out.
Here's working code..Thanks Matze for Crucial, the SIGNAL took me more hours of curiosity.. but doing other things. so ta for a #! moment :-)
def create_sys_tray(self):
self.sysTray = QtGui.QSystemTrayIcon(self)
self.sysTray.setIcon( QtGui.QIcon('../images/corp/blip_32.png') )
self.sysTray.setVisible(True)
self.connect(self.sysTray, QtCore.SIGNAL("activated(QSystemTrayIcon::ActivationReason)"), self.on_sys_tray_activated)
self.sysTrayMenu = QtGui.QMenu(self)
act = self.sysTrayMenu.addAction("FOO")
def on_sys_tray_activated(self, reason):
print "reason-=" , reason
This was an edit of vzades response, but it was rejected on a number of grounds. It does the exact same thing as their code but will also obey the minimize event (and run without syntax errors/missing icons).
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
style = self.style()
# Set the window and tray icon to something
icon = style.standardIcon(QtGui.QStyle.SP_MediaSeekForward)
self.tray_icon = QtGui.QSystemTrayIcon()
self.tray_icon.setIcon(QtGui.QIcon(icon))
self.setWindowIcon(QtGui.QIcon(icon))
# Restore the window when the tray icon is double clicked.
self.tray_icon.activated.connect(self.restore_window)
def event(self, event):
if (event.type() == QtCore.QEvent.WindowStateChange and
self.isMinimized()):
# The window is already minimized at this point. AFAIK,
# there is no hook stop a minimize event. Instead,
# removing the Qt.Tool flag should remove the window
# from the taskbar.
self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.Tool)
self.tray_icon.show()
return True
else:
return super(Example, self).event(event)
def closeEvent(self, event):
reply = QtGui.QMessageBox.question(
self,
'Message',"Are you sure to quit?",
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
event.accept()
else:
self.tray_icon.show()
self.hide()
event.ignore()
def restore_window(self, reason):
if reason == QtGui.QSystemTrayIcon.DoubleClick:
self.tray_icon.hide()
# self.showNormal will restore the window even if it was
# minimized.
self.showNormal()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
This is the correct way to handle double click on a tray icon for PyQt5.
def _create_tray(self):
self.tray_icon = QSystemTrayIcon(self)
self.tray_icon.activated.connect(self.__icon_activated)
def __icon_activated(self, reason):
if reason in (QSystemTrayIcon.Trigger, QSystemTrayIcon.DoubleClick):
pass
This is the code and it does help i believe in show me the code
import sys
from PyQt4 import QtGui, QtCore
from PyQt4.QtGui import QDialog, QApplication, QPushButton, QLineEdit, QFormLayout, QSystemTrayIcon
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.icon = QSystemTrayIcon()
r = self.icon.isSystemTrayAvailable()
print r
self.icon.setIcon(QtGui.QIcon('/home/vzades/Desktop/web.png'))
self.icon.show()
# self.icon.setVisible(True)
self.setGeometry(300, 300, 250, 150)
self.setWindowIcon(QtGui.QIcon('/home/vzades/Desktop/web.png'))
self.setWindowTitle('Message box')
self.show()
self.icon.activated.connect(self.activate)
self.show()
def closeEvent(self, event):
reply = QtGui.QMessageBox.question(self, 'Message', "Are you sure to quit?", QtGui.QMessageBox.Yes |
QtGui.QMessageBox.No, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
event.accept()
else:
self.icon.show()
self.hide()
event.ignore()
def activate(self, reason):
print reason
if reason == 2:
self.show()
def __icon_activated(self, reason):
if reason == QtGui.QSystemTrayIcon.DoubleClick:
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Categories