Connecting to events of another widget - python

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.

Related

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

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.

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.

PyQt user logo in center of MDI Area

I have been working on PyQt QMainWindow QMdiArea Class. I have been able to change the background colour as it is needed for my application.
However, I am unable to add a logo in the centre of the window.
I have tried QBrush but that just insert logo in full QMdiArea.
Furthermore, I have tried paintEvent overridden method but that seems to not work.
Please find enclosed my code and snapshot of the code output below:
# Import necessary libraries
import sys
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtGui import QColor, QBrush, QPainter
from PyQt5.QtWidgets import QStyleFactory, QWidget, QMainWindow, QMdiArea
class MDI_Window(QMainWindow, QWidget):
def __init__(self):
super().__init__()
self.centralWidget = QWidget(self)
self.mdi = QMdiArea()
self.setCentralWidget(self.mdi)
self.window_initialize()
self.show()
def window_initialize(self):
title = 'MDI'
self.setWindowTitle(title)
self.setWindowIcon(QtGui.QIcon("Some_Icon.png"))
self.setMinimumSize(800, 600)
self.mdi.setBackground(QBrush(QColor(169, 169, 169)))
self.showMaximized()
def paintEvent(self, event):
self.mdi.paintEvent(event)
self.painter = QPainter(self)
# For testing logo
self.painter.drawPixmap(500, 500, 500, 500, QtGui.QPixmap("KDS_Main-Window.png"))
if __name__ == "__main__":
# Create App with the design
LMS_App = QtWidgets.QApplication(sys.argv)
LMS_App.setStyle(QStyleFactory.create('Fusion'))
a = MDI_Window()
# Exit application when system is terminated
sys.exit(LMS_App.exec_())
You cannot implement the paintEvent like that, mostly because a paintEvent has to be called by Qt and for the specific widget that is being painted. The paint event must be implemented in the widget.
The easiest solution is to subclass the QMdiArea:
class MdiArea(QMdiArea):
def paintEvent(self, event):
# call the base implementation to draw the default colored background
super().paintEvent(event)
# create the painter *on the viewport*
painter = QPainter(self.viewport())
painter.drawPixmap(500, 500, 500, 500, QtGui.QPixmap("KDS_Main-Window.png"))
Note that:
you shoud remove the paintEvent for the main window, now;
as you can see, the painter is called on the viewport: this is mandatory for all QAbstractScrollArea subclasses;
QPainter instances used in a paintEvent should not be set as instance attributes (as you can see, I didn't use self.painter), as the painter must be destroyed at the end of the function, otherwise you'll face performance and drawing issues; you could theoretically avoid this issue by manually calling painter.end(), but, since a new QPainter instance is most likely going to be recreated very soon and very often, making it a persistent attribute each time has really no use.

PyQt5 signal-slot decorator example

I am currently in the process of creating a class that produces a pyqtSignal(int) and pyqtSlot(int). The difficulty lies in creating a signal that emits a specific value.
Suppose I want to produce something similar to the following simple example:
import sys
from PyQt5.QtCore import (Qt, pyqtSignal, pyqtSlot)
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,
QVBoxLayout, QApplication)
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def printLabel(self, str):
print(str)
#pyqtSlot(int)
def on_sld_valueChanged(self, value):
self.lcd.display(value)
self.printLabel(value)
def initUI(self):
self.lcd = QLCDNumber(self)
self.sld = QSlider(Qt.Horizontal, self)
vbox = QVBoxLayout()
vbox.addWidget(self.lcd)
vbox.addWidget(self.sld)
self.setLayout(vbox)
self.sld.valueChanged.connect(self.on_sld_valueChanged)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Signal & slot')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
My first question for the above code is:
Why use the pyqtSlot() decorator at all when removing pyqtSlot(int) has no effect on the code?
Can you give an example of when it would be necessary?
For specific reasons, I would like to produce my own signal using the pyqtSignal() factory and am given decent documentation here. The only problem however, is that the very simple example does not give a solid foundation for how to emit specific signals.
Here is what I am trying to do but have found myself lost:
Create a metaclass that allows for the implementation of many different types of QWidget subclasses.
Have the metaclass produce its own signal that can be called from outside the class.
This is what I am going for:
from PyQt5.QtWidgets import QPushButton, QWidget
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QSlider
def template(Q_Type, name: str, *args):
class MyWidget(Q_Type):
def __init__(self) -> None:
super().__init__(*args)
self._name = name
def setSignal(self,type):
self.signal = pyqtSignal(type)
def callSignal(self):
pass
return MyWidget
As you can see. I give the widget a name because I find this to be useful, and I also try to instantiate the QWidget from within the class to simplify code.
This is how I would like a main class to produce the widgets from the first example:
import sys
from PyQt5.QtCore import (Qt, pyqtSignal, pyqtSlot)
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,
QVBoxLayout, QApplication)
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def printLabel(self, str):
print(str)
#pyqtSlot(int)
def sld_valChanged(self, value):
self.lcd.display(value)
self.printLabel(value)
def initUI(self):
#instantiate the QWidgets through template class
self.lcd = template(QLCDNumber,'lcd_display')
self.sld = template(QSlider, 'slider', Qt.Horizontal)
#create signal
#self.sld.setSignal(int)
vbox = QVBoxLayout()
vbox.addWidget(self.lcd)
vbox.addWidget(self.sld)
self.setLayout(vbox)
#connect signal - this won't send the value of the slider
#self.sld.signal.connect(self.sld_valChanged)
self.sld.valueChanged.connect(self.sld_valChanged)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Signal & slot')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
There are only 2 problems that need to be solved here:
The template metaclass is set up incorrectly, and I am unable to instantiate the QWidget from within the metaclass. The Q_Type is telling me that its type is PyQt5.QtCore.pyqtWrapperType when it should be PyQt5.QtWidgets.QSlider.
Even though I am creating a newSignal via the templated class, how do I get the QSlider to send the changed value that I want it to send. I believe this is where emit() comes into play but do not have enough knowledge as to how it is useful.
I know I could make some sort of function call self.sld.emit(35) if I would like the signal to pass the value 35 to the slot function. The question becomes not how but where should I implement this function?
I may be totally offbase and overthinking the solution, feel free to correct my code so that I can have my signal emit the value of the slider.
Why use the pyqtSlot() decorator at all when removing pyqtSlot(int)
has no effect on the code? Can you give an example of when it would be
necessary?
Already addressed in your previous question, but I'll re-iterate again.
Although PyQt4 allows any Python callable to be used as a slot when
connecting signals, it is sometimes necessary to explicitly mark a
Python method as being a Qt slot and to provide a C++ signature for
it. PyQt4 provides the pyqtSlot() function decorator to do this.
Connecting a signal to a decorated Python method also has the
advantage of reducing the amount of memory used and is slightly
faster.
Also, as addressed in your previous question, you cannot create signals as instance variables, they must be class attributes.
This will never work
def setSignal(self,type):
self.signal = pyqtSignal(type)
It must be
class MyWidget(QWidget):
signal = pyqtSignal(int)
Create a metaclass that allows for the implementation of many different types of QWidget subclasses.
You cannot define a metaclass for QObjects, since they already use their own metaclass (this is what allows the signal magic to work). However, you can still make a QObject subclass factory using the type function.
def factory(QClass, cls_name, signal_type):
return type(cls_name, (QClass,), {'signal': pyqtSignal(signal_type)})
MySlider = factory(QSlider, 'MySlider', int)
self.sld = MySlider(Qt.Horizontal)
self.sld.signal.connect(self.on_signal)
Have the metaclass produce its own signal that can be called from outside the class.
In short, don't do this. You shouldn't be emitting signals from outside the class. The factory example above isn't very useful, because you're not defining custom methods on those classes to emit those signals. Typically, this is how you would utilize custom signals
class MyWidget(QWidget):
colorChanged = pyqtSignal()
def setColor(self, color):
self._color = color
self.colorChanged.emit()
class ParentWidget(QWidget):
def __init__(self):
...
self.my_widget = MyWidget(self)
self.my_widget.colorChanged.connect(self.on_colorChanged)
# This will cause the colorChanged signal to be emitted, calling on_colorChanged
self.my_widget.setColor(Qt.blue)
def on_colorChanged(self):
# do stuff

PyQt5 focusIN/Out events

I am using Python 3.4 and Qt 5 for the first time. It's easy and I can understand most of functionality which I need. But (there is always "but") I don't understand how to use focusOut/clearFocus/focusIn events.
Am I right that old way:
QObject.connect(self.someWidget, QtCore.SIGNAL('focusOutEvent()'), self.myProcedure)
...does not work in Qt5?
I tried to understand this unsuccessfully. I'll be very thankful for a short example how to catch an event when e.g some of many QLineEdit has lost focus.
The issue here is that focusInEvent/clearFocus/focusOutEvent are not signals, they are event handlers. See for example here. If you want to catch these events you will need to re-implement the event handler on your object, for example by subclassing QLineEdit.
class MyQLineEdit(QLineEdit):
def focusInEvent(self, e):
# Do something with the event here
super(MyQLineEdit, self).focusInEvent(e) # Do the default action on the parent class QLineEdit
In PyQt5 the syntax for signals themselves is simpler. Taking for example the textEdited signal from QLineEdit, you can use that as follows:
self.someWidget.textEdited.connect( self.myProcedure )
This will connect the textEdited signal to your self.myProcedure method. The target method will need to accept the signal outputs, for example:
void textEdited ( const QString & text )
So you could define your self.myProcedure in your class as follows and it will receive the QString sent by that signal.
def myProcedure(self, t):
# Do something with the QString (text) object here
You can also define custom signals as follows:
from PyQt5.QtCore import QObject, pyqtSignal
class Foo(QObject):
an_event = pyqtSignal()
a_number = pyqtSignal(int)
In each of these cases pyqtSignal is used to define a property of the Foo class which you can connect to like any other signal. So for example to handle the above we could create:
def an_event_handler(self):
# We receive nothing here
def a_number_handler(self, i):
# We receive the int
You could then connect() and emit() the signals as follows:
self.an_event.connect( self.an_event_handler )
self.a_number.connect( self.a_number_handler )
self.an_event.emit() # Send the signal and nothing else.
self.a_number.emit(1) # Send the signal wih an int.
The link you posted gives more information on custom signals, signal naming and overloading with the new syntax.
It took me a while to get this figured out so I thought I would post it here to help anyone else having this problem. It is in Python 3.9.6 and PyQt 6.1.2.
This changes the color of the widget when it is in focus and again when it is out of focus.
import sys
from PyQt6 import QtWidgets
class Main(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Qt Testing")
self.setGeometry(0, 0, 640, 120)
h = QtWidgets.QHBoxLayout()
w = ColorQLineEdit("one")
h.addWidget(w)
w = ColorQLineEdit("two")
h.addWidget(w)
self.setLayout(h)
class ColorQLineEdit(QtWidgets.QLineEdit):
def focusInEvent(self, event):
print("in")
self.setStyleSheet("background-color: yellow; color: red;")
super().focusInEvent(event)
def focusOutEvent(self, event):
print("out")
self.setStyleSheet("background-color: white; color: black;")
super().focusOutEvent(event)
app = QtWidgets.QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec())

Categories