pyqt5 - closing/terminating application
Asked Answered
A

4

10

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.

Alenealenson answered 11/4, 2016 at 17:18 Comment(0)
E
15

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)
Electrokinetic answered 11/4, 2016 at 18:40 Comment(2)
working now, thank you. Incidentally I can't seem to set the button order in my dialog regardless of how I order them in this line: QMessageBox.Save | QMessageBox.Close | QMessageBox.Cancel. I'm assuming it auto sets them in ascending alphabetical order?Alenealenson
@user3548783. It must be using a QDialogButtonBox, which orders the buttons in whatever way is appropriate for the current platform and/or widget style. You can always create your own message-box class if you want different behaviour.Electrokinetic
L
1

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.

Laager answered 11/4, 2016 at 18:41 Comment(2)
Did you actually test any of this? Calling destroy() will delete the widget and leave the application running with no means to stop it, which is certainly not the right thing to do. Also, as the OP already said, the keyPressEvent works, so there is no need to change it.Electrokinetic
@Electrokinetic No I did not. It's corrected. I just wanted to create the alternative to the event.accept() that does not work in this case. Thank you for the heads up. As for the esc, I did not say otherwise. I just explained what was happening and provided the code for overriding the default behavior in case the user ever needs to. It was a matter of being more complete in my answer.Laager
C
0

closeEvent()

In above right tick mark code please check the closeEvent it is same other wise it's waste of time to research.

Carte answered 16/3, 2022 at 10:58 Comment(0)
L
0

My solution:

.
.
.

def __message(self):
    if not isinstance(self.box, qtw.QMessageBox):
        self.box = qtw.QMessageBox.question(
            self,
            "Quit",
            "Are you sure?",
            qtw.QMessageBox.No | qtw.QMessageBox.Yes,
        )

def keyPressEvent(self, k):
    if k.key() == qtc.Qt.Key.Key_Escape:
        self.__message()
        if self.box == qtw.QMessageBox.Yes:
            sys.exit(qtw.QApplication.exit())

def closeEvent(self, e):
    self.__message()
    if self.box == qtw.QMessageBox.Yes:
        e.accept()
    else:
        e.ignore()
Lane answered 23/4 at 14:30 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Upbraid

© 2022 - 2024 — McMap. All rights reserved.