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

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

Related

Handle mousePressEvent and leaveEvent of QPushButton at the same time

I have a pushButton and want to have a specific behaviour while users interact with that button.
Here is what I want:
user pressed on the button (Without release)
Then the user moved the cursor outside the button (Still the mouse is not released yet).
So, the button should take an active icon and whenever he points outside the button that icon should be changed to inactive-icon
Here is what I get:
I made my own QPushButton and overridden both (leaveEvent(), mouseReleaseEvent(), mousePressEvent()), But the problem is that after the user press and keep pressing on that button no other events are handled, So I need a way to handle other events like the leaveEvent()
Here is my own button class:
class ToggledButton(QPushButton):
def __init__(self, icon: QIcon = None, active_icon: QIcon = None):
super(ToggledButton, self).__init__()
self.active_icon= active_icon
self.inactive_icon = icon
self.setIcon(icon)
self.setCheckable(True)
self.setFlat(False)
def leaveEvent(self, event):
super(ToggledButton, self).leaveEvent(event)
self.setIcon(self.inactive_icon )
def mousePressEvent(self, event):
super(ToggledButton, self).mousePressEvent(event)
self.setIcon(self.active_icon)
def mouseReleaseEvent(self, event):
super(ToggledButton, self).mouseReleaseEvent(event)
self.setIcon(self.inactive_icon )
When a widget receives a mouse button press, it normally becomes the mouse grabber. When that happens, no widget will ever receive an enter or leave event, including that same widget.
A widget that is the mouse grabber will always receive mouse move events, so that is the function that you need to override:
def mouseMoveEvent(self, event):
super().mouseMoveEvent(event)
if event.buttons() == Qt.LeftButton:
if event.pos() in self.rect():
self.setIcon(self.active_icon)
else:
self.setIcon(self.inactive_icon)
Note: you should check the button() in the mouse press event, otherwise it will change icon also when the user presses a different mouse button:
def mousePressEvent(self, event):
super().mousePressEvent(event)
if event.button() == Qt.LeftButton:
self.setIcon(self.active_icon)
Note the difference between buttons() (the pressed mouse buttons at the time of the event) and button(), which is the mouse button that caused the event, which is obviously meaningless for mouse move events, since the event is not caused by mouse buttons but by the movement.

PyQt5 closeEvent method

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

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.

How do I bind wx.CLOSE_BOX to a function?

Fairly simple question, but I can't seem to find the answer. I have a GUI which has a cancel button that asks the user to abort all unsaved changes when they press it. The GUI also has a wx.CLOSE_BOX, but this simply closes it because its not bound to my OnCancel function. How do I bind it?
Things I tried:
self.Bind(wx.EVT_CLOSE, lambda event: self.OnCancel(event, newCRNum), wx.CLOSE_BOX)
#This gives an AssertionError, replacing wx.EVT_CLOSE with wx.EVT_BUTTON also
# gives the same error
self.Bind(wx.EVT_CLOSE, lambda event: self.OnCancel(event, newCRNum))
#This binds any time ```self.Close(True)``` occurs (which makes sense) but
# is not what I want. There are other buttons which close the GUI which should not
# use the OnCancel function
Thanks in advance for your help
EDIT: The code below should help clarify what I'm looking for
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
newCRNum = 0
cancelBtn = wx.Button(self, -1, "Cancel")
self.Bind(wx.EVT_BUTTON, lambda event: self.OnCancel(event, newCRNum), cancelBtn)
def OnCancel(self, event, CRNum):
dlg = wx.MessageDialog(self, "Are you sure you want to cancel? All work will be lost and CR number will not be reserved.", "Cancel CR", wx.YES_NO|wx.NO_DEFAULT|wx.ICON_EXCLAMATION)
if dlg.ShowModal() == wx.ID_YES:
self.Destroy()
else:
dlg.Destroy
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show()
app.MainLoop()
So what this does is create a whimsically large cancel button. When this button is pressed, a dialog box pops up and prompts the user if they really want to quit. If they say yes, the whole gui closes, if not, only the dialog box closes.
When the user presses the red (X) button in the top right of the GUI, I want the same thing to happen. Since is a button, I assume it can be bound to my OnCancel button, but how do I do this?
Reason for AssertionError: The third argument must be source widget like follow.
self.Bind(wx.EVT_CLOSE, lambda event: self.OnCancel(event, newCRNum), self)
Try following example:
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
newCRNum = 0
self.Bind(wx.EVT_CLOSE, lambda event: self.OnCancel(event, newCRNum))
def OnCancel(self, event, num):
dlg = wx.MessageDialog(self, 'Do you want close?', 'Sure?',
wx.OK|wx.CANCEL|wx.ICON_QUESTION)
result = dlg.ShowModal()
if result == wx.ID_OK:
event.Skip()
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show()
app.MainLoop()

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)

Categories