Connecting to events of another widget

Viewed 2655

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))
3 Answers

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