PyQt5: How to use closeEvent with a custom message-box - python

Before I close my main window, I want to do some last operations.
I think the closeEvent is the right thing to do that, but the standard QMessageBox does not suit my design, so I want to make my own (and I did). But with the following code, the application closes directly without showing my message-box.
What here is the problem?
def closeEvent(self, event):
self.messageBox = lastWindow(self)
self.messageBox.show()
return super().closeEvent(event)
This is the lastWindow code: (subclass of a pyuic5 generated .ui file):
from PyQt5 import QtCore
from PyQt5.QtGui import QColor
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtWidgets import QDialog, QGraphicsDropShadowEffect
from UIs.UI_quit import Ui_Quit
class lastWindow(QDialog, Ui_Quit):
def __init__(self, parent = None):
super(lastWindow, self).__init__(parent)
self.setupUi(self)
self._parent = parent
self.setWindowFlag(QtCore.Qt.WindowType.FramelessWindowHint)
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground)
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
self.shadow = QGraphicsDropShadowEffect(self)
self.shadow.setBlurRadius(10)
self.shadow.setXOffset(5)
self.shadow.setYOffset(5)
self.shadow.setColor(QColor(0, 0, 0, 80))
self.mainFrame.setGraphicsEffect(self.shadow)
self.btn_close.clicked.connect(self.abort)
self.btn_quit.clicked.connect(self.destroy)
self.btn_abort.clicked.connect(self.abort)
self._parent.blur.setEnabled(True)
def destroy(self):
self._parent.close()
self.close()
def abort(self):
self._parent.startService()
self.close()

Use the built-in accept and reject slots to close the dialog, and then use the return value of exec in your closeEvent to decide what to do next:
class lastWindow(QDialog, Ui_Quit):
...
def destroy(self):
self.accept()
def abort(self):
self.reject()
class MainWindow(QMainWindow):
...
def closeEvent(self, event):
dialog = lastWindow(self)
if dialog.exec() == QDialog.Rejected:
self.startService()
event.ignore()
Calling event.ignore() will prevent the window closing; otherwise, the event will be accepted by default and the window will close. Note that, unlike show(), the exec() call blocks (i.e. it waits for user interaction), which explains why your current example doesn't work as you expected.

Related

How do I pass a value from one class to another without inheritance?

I have two classes. the Main class is responsible for the main window in which the gif is played, and the Settings class represents a window in which you can select a gif. I need to pass the name of the gif selected in the Settings window so that it immediately starts playing in the Main window. How can I do this?
Part of the program related to this problem:
import sys
import traceback
from PyQt5 import uic
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget
def except_hook(cls, exception, traceback):
sys.__excepthook__(cls, exception, traceback)
class Settings(QWidget):
def __init__(self):
super().__init__()
self.widget = QWidget()
uic.loadUi('settings.ui', self)
self.PB_OK.clicked.connect(self.close_this_window)
self.all_gifs = ['cosmowave1.gif', 'cosmowave2', 'cosmowave3', 'retrowave1.gif', 'retrowave2.gif']
self.CB_chooseGifs.addItems(self.all_gifs)
def close_this_window(self):
Main.gif = self.CB_chooseGifs.currentText()
self.close()
class Main(QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi('mainWindow.ui', self)
gif = 'cosmowave1.gif'
self.gif_image = f'gifs/{gif}'
self.settings_window = Settings()
self.settings_PB.clicked.connect(self.settings)
self.movie = QMovie(self)
self.add_a_gif(self.gif_image)
def add_a_gif(self, gif_image):
self.movie.setFileName(gif_image)
self.GIF_LBL.setMovie(self.movie)
self.movie.start()
def settings(self):
self.settings_window.show()
if __name__ == '__main__':
sys.excepthook = except_hook
app = QApplication(sys.argv)
ex = Main()
ex.show()
sys.exit(app.exec_())
I am not familiar specifically with PyQt5 but the usual technique for this sought of thing is to specify a "callback" function to be invoked when a certain event occurs, in this case when the settings windows is closed:
Modify the Setttings constructor to take such a callback argument and then modify method close_this_window to invoke the callback when it closes:
class Settings(QWidget):
def __init__(self, onclose):
super().__init__()
self.onclose = onclose
...
def close_this_window(self):
self.close()
self.onclose(self.CB_chooseGifs.currentText())
Then the Main constructor just needs to construct the Settings instance as follows so that method add_a_gif is called when the settings window is closed:
class Main(QMainWindow):
def __init__(self):
super().__init__()
...
self.settings_window = Settings(self.add_a_gif)
...
For a more general solution, investigate the Observer Pattern.

How to do override closeEvent functions to make it working

I'm using pyside2 and pyqt5 lib to loading my UI file.
from PySide2 import QtWidgets
from PySide2.QtWidgets import QApplication
from PySide2.QtUiTools import QUiLoader
class A(QtWidgets.QWidget):
def __init__(self, parent=None):
super(A, self).__init__(parent)
self.ui = QUiLoader().load('uiFile.ui')
def closeEvent(self, event):
event.ignore()
app = QApplication([])
mainWin = A()
mainWin.ui.show()
app.exec_()
In my thought, it will show '11111' when I clicked the X button.
However, it's not working at all.
here is the ui file
The problem is that "A" is a widget that is not displayed, it is not the window, so override closeEvent does not make sense. Instead you should use an event filter to monitor the window's events.
from PySide2.QtCore import QEvent, QObject
from PySide2.QtWidgets import QApplication
from PySide2.QtUiTools import QUiLoader
class Manager(QObject):
def __init__(self, ui, parent=None):
super(Manager, self).__init__(parent)
self._ui = ui
self.ui.installEventFilter(self)
#property
def ui(self):
return self._ui
def eventFilter(self, obj, event):
if obj is self.ui:
if event.type() == QEvent.Close:
event.ignore()
return True
super().eventFilter(obj, event)
def main():
app = QApplication([])
manager = Manager(QUiLoader().load("uiFile.ui"))
manager.ui.show()
app.exec_()
if __name__ == "__main__":
main()
If you want to override the closeEvent method of the QMainWindow used in the .ui then you must promote it and register it as this other answer shows.

Extending a Widget Loaded from a UI File

I'd like to be able to load a widget (QMainWindow in this case) from a .ui file, but also be able to extend the class so that I can do things like catch keyboard events.
For example, I have the following that doesn't work but shows what I'm trying to accomplish:
class MyMainWindow(QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
self.window = QUiLoader().load("mainwindow.ui", self)
self.setCentralWidget(self.window)
self.window.keyPressEvent = self.key_pressed
self.window.setEnabled(True)
def show(self):
self.window.show()
def key_pressed(self, event):
print(event)
Because I cannot extend the object loaded from QUiLoader, I attempted to hijack the keyPressEvent method in that object, and assign it to my own key_pressed method. This doesn't work, but I'm unsure how else to go about capturing keyboard events.
I know I could create MyMainWindow and set it's base class to QMainWindow, and then override the keyPressEvent method, but then I have to do all the layout in code, and I'd much rather leverage the .ui file. How do you go about doing this?
To listen to events from other widgets you must use an eventFilter. Also the initial window is never displayed so it is better to replace it with a QObject.
from PySide2 import QtCore, QtWidgets, QtUiTools
class MyApp(QtCore.QObject):
def __init__(self):
super(MyApp, self).__init__()
self.window = QtUiTools.QUiLoader().load("mainwindow.ui")
self.window.installEventFilter(self)
def show(self):
self.window.show()
def eventFilter(self, obj, event):
if obj is self.window:
if event.type() == QtCore.QEvent.KeyPress:
print(event.key())
return super(MyApp, self).eventFilter(obj, event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MyApp()
w.show()
sys.exit(app.exec_())

Connecting to events of another widget

This is most likely a duplicate question, but I have to ask it because other answers aren't helping in my case, since I am new to pyqt (switched from tkinter few days ago).
I am wondering if is it possible to connect to an event of a widget like this:
self.lineEdit = QtGui.QLineEdit(self.frame)
self.lineEdit.keyReleaseEvent(lambda: someFunction(QtCore.Qt.Key_A ))
self.lineEdit.setObjectName(_fromUtf8("lineEdit"))
self.horizontalLayout.addWidget(self.lineEdit)
and then...
def someFunction(event):
print(event)
...
My question is how to bind to a specific event from another widget, and connect that event with a function - like btn.clicked.connect(function_goes_here).
In tkinter it's something be like this:
self.Entry.bind("<KeyRelease-a>", lambda event: someFunction(event))
There are a number of different ways to achieve this. A generic way to listen to all events for a given widget, is to install an event-filter on it. All protected functions have a corresponding event type that can be accessed in this way:
class MainmWindow(QMainWindow):
def __init__(self):
...
self.lineEdit = QLineEdit(self.frame)
self.lineEdit.installEventFilter(self)
def eventFilter(self, source, event):
if source is self.lineEdit:
if event.type() == QEvent.KeyRelease:
print('key release:', event.key())
# the following line will eat the key event
# return True
return super(MainmWindow, self).eventFilter(source, event)
Alternatively, you can sub-class the widget, re-implement the relevant event handler, and emit a custom signal:
class LineEdit(QLineEdit):
keyReleased = pyqtSignal(int)
def keyReleaseEvent(self, event):
self.keyReleased.emit(event.key())
super(LineEdit, self).keyReleaseEvent(event)
class MainmWindow(QMainWindow):
def __init__(self):
...
self.lineEdit = LineEdit(self.frame)
self.lineEdit.keyReleased.connect(self.handleKeyRelease)
def handleKeyRelease(self, key):
print('key release:' key)
A more hackish variation on this is to overwrite the method directly:
class MainmWindow(QMainWindow):
def __init__(self):
...
self.lineEdit = QLineEdit(self.frame)
self.lineEdit.keyReleaseEvent = self.handleKeyRelease
def handleKeyRelease(self, event):
print('key release:', event.key())
QLineEdit.keyReleaseEvent(self.lineEdit, event)
Note that if you don't want to invoke the default event handling, you can omit the call to the base-class method.
I don't have enough reputation to reply to #ekhumoro, but wanted to add to their answer with an applied answer specific to MouseEvents for those who might benefit, based on a similar issue I was having.
Scenario
Display an image in the central widget, and connect a custom signal to capture mouse double click actions in the central widget.
Source for test image: https://en.wikipedia.org/wiki/Standard_test_image#/media/File:SIPI_Jelly_Beans_4.1.07.tiff
Consider the following:
A basic QMainWindow class which loads a supplied image_file and shows the image in a custom sub-class of the QLabel class, fit to the window:
import sys
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QPixmap, QImage, QPalette, QMouseEvent
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QScrollArea)
class ImageView(QMainWindow):
def __init__(self):
super().__init__()
self.initializeUI()
self.image = QImage()
def initializeUI(self):
self.setMinimumSize(300, 200)
self.setWindowTitle("Image Viewer")
# self.showMaximized()
self.createMainQLabel()
self.show()
def createMainQLabel(self):
"""Create an instance of the imageQLabel class and set it
as the main window's central widget."""
self.image_qlabel = imageQLabel(self)
self.image_qlabel.resize(self.image_qlabel.pixmap().size())
self.scroll_area = QScrollArea()
self.scroll_area.setBackgroundRole(QPalette.Dark)
self.scroll_area.setAlignment(Qt.AlignCenter)
self.scroll_area.setWidget(self.image_qlabel)
self.setCentralWidget(self.scroll_area)
class imageQLabel(QLabel):
"""Subclass of QLabel for displaying image"""
def __init__(self, parent, image_file="images/SIPI_Jelly_Beans_4.1.07.tiff"):
super().__init__(parent)
self.openImage(image_file)
self.setScaledContents(True)
self.setAlignment(Qt.AlignCenter)
def openImage(self, image_file):
# Get image, create the pixmap and resize to fit window
self.image = QImage(image_file)
self.setPixmap(QPixmap().fromImage(self.image))
self.resize(self.pixmap().size())
"""
Other methods used to customize the class, for example:
- Resize
- Rotate
- Crop
- ...
...
"""
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setAttribute(Qt.AA_DontShowIconsInMenus, True)
window = ImageView()
sys.exit(app.exec_())
When executed, yield a simple window with the image:
SIPI Test Image shown in PyQT5 Window
What we would like to do is add a mouseDoubleClickEvent to the imageQLabel sub-class, but ensure that other widgets or classes in the program can access the event signals. To do this, you can modify #ekhumoro's answer #2 to accommodate a pyqtSignal.
First, add the signal to the attributes of the imageQLabel class. Here, we need the full QMouseEvent rather than just an int:
import sys
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QPixmap, QImage, QPalette, QMouseEvent
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QScrollArea)
class imageQLabel(QLabel):
"""Subclass of QLabel for displaying image"""
# Class attributes
mouseDoubleClickSignal = pyqtSignal(QMouseEvent)
We wish to pass the event from that pyqtSignal to some other method in our ImageView main window class. As an example, let's make a method to print the cursor position when the user double clicks in the imageQLabel sub-class.
In ImageView:
class ImageView(QMainWindow):
"""
...
The rest of the class code
...
"""
def print_mouse(self, event):
print("print_mouse event from QMainWindow: {}".format((event.x(), event.y())))
Now we just need to connect print_mouse to the custom pyqtSignal we made. It makes the most sense to do this in the initializeUI method:
def initializeUI(self):
self.setMinimumSize(300, 200)
self.setWindowTitle("Image Viewer")
# self.showMaximized()
self.createMainQLabel()
# Connect the signal from custom imageQLabel class
# to the QMainWindow
self.image_qlabel.mouseDoubleClickSignal.connect(self.print_mouse)
self.show()
These modifications now print the cursor position when double clicking inside of the imageQLabel sub-class in the central widget. The call to print_mouse is outside of the sub-class. Moreover, double-clicking outside of the central widget (like outside of the frame of the image) does not call print_mouse because the mouseDoubleClickEvent is only active for the central widget.
%user profile%\AppData\Local\Programs\Python\Python39\python.exe
%script path%\stackoverflowsolution.py
print_mouse event from QMainWindow: (115, 140)
print_mouse event from QMainWindow: (54, 55)
override using python lambda magic
def on_key(line_edit : QLineEdit, key):
print(key)
...
line_edit = self._lineedit = QLineEdit()
line_edit.keyReleaseEvent = lambda ev, self=line_edit, super=line_edit.keyReleaseEvent: \
(super(ev), on_key(self, ev.key()), None)[-1]
We save current keyReleaseEvent function in lambda kwarg super.
Also we need to save line_edit in lambda kwarg self, because line_edit will be removed from locals during lambda execution.
Finally we need to return None, thus get it from last element from our Tuple.

How to detect any mouse click on PySide Gui?

I am trying implement a feature such that when a mouse is clicked on the gui, a function is triggered
Below is my mouse click detection, it doesn't work when I click on any part of the gui
from PySide.QtCore import *
from PySide.QtGui import *
import sys
class Main(QWidget):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QHBoxLayout(self)
layout.addWidget(QLabel("this is the main frame"))
layout.gui_clicked.connect(self.anotherSlot)
def anotherSlot(self, passed):
print passed
print "now I'm in Main.anotherSlot"
class MyLayout(QHBoxLayout):
gui_clicked = Signal(str)
def __init__(self, parent=None):
super(MyLayout, self).__init__(parent)
def mousePressEvent(self, event):
print "Mouse Clicked"
self.gui_clicked.emit("emit the signal")
a = QApplication([])
m = Main()
m.show()
sys.exit(a.exec_())
This is my goal
Mouseclick.gui_clicked.connect(do_something)
Any advice would be appreciated
Define mousePressEvent inside Main:
from PySide.QtCore import *
from PySide.QtGui import *
import sys
class Main(QWidget):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QHBoxLayout(self)
layout.addWidget(QLabel("this is the main frame"))
def mousePressEvent(self, QMouseEvent):
#print mouse position
print QMouseEvent.pos()
a = QApplication([])
m = Main()
m.show()
sys.exit(a.exec_())
This can get complicated depending on your needs. In short, the solution is an eventFilter installed on the application. This will listen the whole application for an event. The problem is "event propagation". If a widget doesn't handle an event, it'll be passed to the parent (and so on). You'll see those events multiple times. In your case, for example QLabel doesn't do anything with a mouse press event, therefore the parent (your main window) gets it.
If you actually filter the event (i.e. you don't want the original widget to respond to the event), you won't get that problem. But, I doubt that this is your intent.
A simple example for just monitoring:
import sys
from PySide import QtGui, QtCore
class MouseDetector(QtCore.QObject):
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
print 'mouse pressed', obj
return super(MouseDetector, self).eventFilter(obj, event)
class MainWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
layout = QtGui.QHBoxLayout()
layout.addWidget(QtGui.QLabel('this is a label'))
layout.addWidget(QtGui.QPushButton('Button'))
self.setLayout(layout)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mouseFilter = MouseDetector()
app.installEventFilter(mouseFilter)
main = MainWindow()
main.show()
sys.exit(app.exec_())
You can see that, clicking on the QLabel will give you something like:
mouse pressed <PySide.QtGui.QLabel object at 0x02B92490>
mouse pressed <__main__.MainWindow object at 0x02B92440>
Because, QLabel receives the event and since it doesn't do anything with it, it's ignored and passed to the parent (MainWindow). And it's caught by the filter/monitor again.
Clicking on the QPushButton doesn't have any problem because it uses that event and does not pass to the parent.
PS: Also note that this can cause performance problems since you are inspecting every single event in the application.

Categories