Python qt4 - passing arguments in QtCore.QObject.connect

3.7k views Asked by At

I want to react to a mouse click on a QLabel.
To achieve this I have redefined the method mouseReleaseEvent of QLabel.
I want to pass two arguments to the slot:

  • the QtGui.QMouseEvent
  • an ID of the clicked QLabel

But I can only pass one parameter. Either QtGui.QMouseEvent or the ID.
The combination does not work.

# -*- coding: utf-8 -*-

import sys
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import pyqtSignal

class ExtendedQLabel(QtGui.QLabel):
    
    #labelClickSignal_1 = pyqtSignal(QtGui.QMouseEvent)
    labelClickSignal_1 = pyqtSignal(QtGui.QMouseEvent, int)
    labelClickSignal_2 = pyqtSignal()
 
    def __init(self, parent):
        QtGui.QLabel.__init__(self, parent)
        
    # redefinition 
    def mouseReleaseEvent(self, event):
        
        #self.labelClickSignal_1.emit(event)
        self.labelClickSignal_1.emit(event, 0)
        self.labelClickSignal_2.emit()
        

class Test(QtGui.QMainWindow):

    def __init__(self, parent=None):
        
        QtGui.QMainWindow.__init__(self, parent)
        
        self.names = ['Test1', 'Test2', 'Test3']

        self.centralWidget = QtGui.QWidget()
        self.setCentralWidget(self.centralWidget)
        
        self.grid = QtGui.QGridLayout(self.centralWidget)
        
        row = 0
        for name in self.names:
            self.addLabel(name, row)
            row = row + 1
            
    def addLabel(self, name, row):
       
        label = ExtendedQLabel(name)
        
        # QtGui.QMouseEvent is automatically passed to the slot
        label.labelClickSignal_1.connect(self.onLabelClicked_1)
        
        # The row ID is passed to the slot
        label.labelClickSignal_2.connect(lambda id = row: 
                                 self.onLabelClicked_2(id))
                                 
        # *This does not work*
        label.labelClickSignal_1.connect(lambda id = row: 
                                 self.onLabelClicked_3(QtGui.QMouseEvent, id))
                                 
        self.grid.addWidget(label, row, 1)
       
        row = row + 1
        
    def onLabelClicked_1(self, event):
        
        if event.button() == QtCore.Qt.RightButton:
            print('right')
        else:
            print('left')

    def onLabelClicked_2(self, id):
        
        print('Label {0} clicked'.format(id))
        
    def onLabelClicked_3(self, event, id):

        # *This does not work*
        if event.button() == QtCore.Qt.RightButton:
            print('right {0}'.format(id))
        else:
            print('left {0}'.format(id))

def main():        
    app = QtGui.QApplication(sys.argv)
    
    t = Test()
    t.show()
    
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()
2

There are 2 answers

2
Mailerdaimon On BEST ANSWER

Ok, since your code had several pieces that did not work I rewrote the important Parts to achieve what you want. Please Note that I use PySide and not PyQt. This means you have to change the importe Statements and the Signal back to PyQt Notation. The rest is explained in the code.

import sys
from PySide import QtGui, QtCore

class ExtendedQLabel(QtGui.QLabel):

    #Signal that emits on MouseRelease
    labelClickSignal_1 = QtCore.Signal(QtGui.QMouseEvent, int)

    # init to -1
    labelId = -1

    # This is the new Constructor, Please note the double underscore 
    # before and behind `init`
    def __init__(self, parent, labelId):
        self.labelId = labelId
        QtGui.QLabel.__init__(self, parent)

    # emit labelClickSignal
    def mouseReleaseEvent(self, event):
        self.labelClickSignal_1.emit(event, self.labelId)


class Test(QtGui.QMainWindow):

    def __init__(self, parent=None):

       # same as yours [...]

    def addLabel(self, name, row):

        # please note the added argument
        label = ExtendedQLabel(name,row)

        # connect the signal
        label.labelClickSignal_1.connect(self.onLabelClicked_1)

        self.grid.addWidget(label, row, 1)

        row = row + 1

    def onLabelClicked_1(self, event,labelId):    
        if event.button() == QtCore.Qt.RightButton:
            print('right')
            print(labelId)
        else:
            print('left')
            print(labelId)

OLD ANSWER You have to define your Signal to support your two arguments:

labelClickSignal_1 = pyqtSignal(QtGui.QMouseEvent,int)

See here for additional information.

Example from the docs:

from PyQt4.QtCore import QObject, pyqtSignal

class Foo(QObject):

    # This defines a signal called 'closed' that takes no arguments.
    closed = pyqtSignal()

    # This defines a signal called 'rangeChanged' that takes two
    # integer arguments.
    range_changed = pyqtSignal(int, int, name='rangeChanged')

    # This defines a signal called 'valueChanged' that has two overloads,
    # one that takes an integer argument and one that takes a QString
    # argument.  Note that because we use a string to specify the type of
    # the QString argument then this code will run under Python v2 and v3.
    valueChanged = pyqtSignal([int], ['QString'])
2
koffein On

I think I solved it. Your lambda with the comment *and this does not work* took only one argument, though the slot is defined to take two. So I just skipped the first argument.

Next issue: It is difficult to pass Events that have been generated by system. I think they are destroyed after they have been handled. So I copied the data of the event into a namedtuple and passed this instead. (I also tried to copy the event, but this did not work somehow.)

# -*- coding: utf-8 -*-

import sys
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import pyqtSignal
from collections import namedtuple

class ExtendedQLabel(QtGui.QLabel):
    MouseEventTuple = namedtuple('MouseEventTuple', 'type pos button buttons modifiers')
    #labelClickSignal_1 = pyqtSignal(QtGui.QMouseEvent)
    labelClickSignal_1 = pyqtSignal(MouseEventTuple, int)
    labelClickSignal_2 = pyqtSignal()

    def __init(self, parent):
        QtGui.QLabel.__init__(self, parent)

    # redefinition 
    def mouseReleaseEvent(self, event):

        eventTuple =  ExtendedQLabel.MouseEventTuple(type = event.type(), pos = event.pos(), button = QtCore.Qt.RightButton, buttons = event.buttons(), modifiers = event.modifiers())
        self.labelClickSignal_1.emit(eventTuple, 0)
        self.labelClickSignal_2.emit()


class Test(QtGui.QMainWindow):

    def __init__(self, parent=None):

        QtGui.QMainWindow.__init__(self, parent)

        self.names = ['Test1', 'Test2', 'Test3']

        self.centralWidget = QtGui.QWidget()
        self.setCentralWidget(self.centralWidget)

        self.grid = QtGui.QGridLayout(self.centralWidget)

        row = 0
        for name in self.names:
            self.addLabel(name, row)
            row = row + 1

    def addLabel(self, name, row):

        label = ExtendedQLabel(name)

        # QtGui.QMouseEvent is automatically passed to the slot
        label.labelClickSignal_1.connect(self.onLabelClicked_1)

        # The row ID is passed to the slot
        label.labelClickSignal_2.connect(lambda id = row: 
                                 self.onLabelClicked_2(id))

        # *This works now*
        label.labelClickSignal_1.connect(lambda _unused_, id = row: 
                                 self.onLabelClicked_3(QtGui.QMouseEvent, id))

        self.grid.addWidget(label, row, 1)

        row = row + 1

    def onLabelClicked_1(self, eventTuple):

        if eventTuple.button == QtCore.Qt.RightButton:
            print('right')
        else:
            print('left')

    def onLabelClicked_2(self, id):

        print('Label {0} clicked'.format(id))

    def onLabelClicked_3(self, eventTuple, id):

        # *This does not work*
        if eventTuple.button == QtCore.Qt.RightButton:
            print('right {0}'.format(id))
        else:
            print('left {0}'.format(id))

def main():        
    app = QtGui.QApplication(sys.argv)

    t = Test()
    t.show()

    sys.exit(app.exec_())

if __name__ == "__main__":
    main()