How to pass arguments to callback functions in PyQt

17,661

Solution 1

You can send the action object itself using a signal mapper. However, it may be better to simply send an identifier and do all the work within the signal handler.

Here's a simple demo script:

from PyQt4 import QtGui, QtCore

class Window(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.mapper = QtCore.QSignalMapper(self)
        self.toolbar = self.addToolBar('Foo')
        self.toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
        for text in 'One Two Three'.split():
            action = QtGui.QAction(text, self)
            self.mapper.setMapping(action, text)
            action.triggered.connect(self.mapper.map)
            self.toolbar.addAction(action)
        self.mapper.mapped['QString'].connect(self.handleButton)
        self.edit = QtGui.QLineEdit(self)
        self.setCentralWidget(self.edit)

    def handleButton(self, identifier):
        if identifier == 'One':
            text = 'Do This'
        elif identifier == 'Two':
            text = 'Do That'
        elif identifier == 'Three':
            text = 'Do Other'
        self.edit.setText(text)

if __name__ == '__main__':

    import sys
    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.resize(300, 60)
    window.show()
    sys.exit(app.exec_())

Solution 2

How to pass arguments to callback functions in PyQt

You can use functools.partial from standart Python library. Example with QAction:

some_action.triggered.connect(functools.partial(some_callback, param1, param2))

Solution 3

You could use a lambda function associated to the GUI control's slot to pass extra arguments to the method you want to execute.

# Create the build button with its caption
self.build_button = QPushButton('&Build Greeting', self)
# Connect the button's clicked signal to AddControl
self.build_button.clicked.connect(lambda: self.AddControl('fooData'))
def AddControl(self, name):
    print name

Source: snip2code - Using Lambda Function To Pass Extra Argument in PyQt4

Solution 4

The best way to pass the arguments is to not pass them at all. You can use the dynamic nature of python, and set whatever data you need as your own properties on the widgets themselves, get the target widget in the handler using self.sender(), and then get whatever properties you need directly from the widget.

In this example five buttons are created and the required state is set on the button widget as my_own_data property:

import sys
from PyQt4 import QtGui, QtCore

class Main(QtGui.QMainWindow):

    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.centralwidget = QtGui.QWidget()
        self.vboxlayout = QtGui.QVBoxLayout()

        for idx in range(5):
            button = QtGui.QPushButton('button ' + str(idx), None)
            button.my_own_data = str(idx)  # <<< set your own property
            button.clicked.connect(self.click_handler)  # <<< no args needed
            self.vboxlayout.addWidget(button)

        self.centralwidget.setLayout(self.vboxlayout)
        self.setCentralWidget(self.centralwidget)
        self.show()

    def click_handler(self, data=None):
        if not data:
            target = self.sender()  # <<< get the event target, i.e. the button widget
            data = target.my_own_data  # <<< get your own property
        QtGui.QMessageBox.information(self, 
                                      "you clicked me!", 
                                      "my index is {0}".format(data))

app = QtGui.QApplication(sys.argv)
main = Main()
main.show()

sys.exit(app.exec_())

Solution 5

There is still a bug in the way PyQt uses QSignalMapper. I discovered a work-around here:

http://www.riverbankcomputing.com/pipermail/pyqt/2010-March/026113.html

To fix the answered question make the following change:

        action.triggered[()].connect(self.mapper.map)

This was needed for the following version of Python:

Python 2.6.6 (r266:84292, Feb 21 2013, 19:26:11)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-3)] on linux2
Share:
17,661
yasar
Author by

yasar

I am an economics student, who enjoys programming.

Updated on June 22, 2022

Comments

  • yasar
    yasar almost 2 years

    I have around 10 QAction (this number will vary in runtime) in a toolbar, which all will do same thing, but using different parameters. I am thinking to add parameter as an attribute to QAction object, and then, QAction's triggered signal will also send object's itself to the callback function, so that I could get required parameters for the function. I have actually 2 questions about this:

    • Can it be done?
    • Is there a better way of doing this?
  • 101
    101 almost 9 years
    This answer doesn't seem very popular. Is QSignalMapper not widely used?
  • ekhumoro
    ekhumoro almost 9 years
    @figs. I don't know why the OP accepted this answer, given that the more conventional lambda/partial solution was already given. There was once a time when QSignalMapper was more useful, because PyQt hasn't always supported using lambda/partial as a slot - but that was a long time ago. At the moment, though, I can't think of a scenario where it might be more effective (other than in C++, of course).
  • ekhumoro
    ekhumoro over 8 years
    There are some downsides to this approach, which are set out in the docs for sender. As you've written it, click_handler cannot be safely called by anything other than the specific buttons that are connected to it.
  • ccpizza
    ccpizza over 8 years
    @ekhumoro: Agreed; added optional data parameter, so that click_handler() can be used in a more generic way.