Trouble Understanding Signal Mapper PyQt

1.7k views Asked by At

So I am generating a menu of options based on some files on my system. I have a list list of objects I need to dynamically generate an option in the menu for and need to be able to let the function that is doing the creation know which object to use. After some research I found the post below. I could not comment as my rep is not high yet: How to pass arguments to callback functions in PyQt

When I run this the signal mapper is not working right. It is not even calling the handleButton correctly. Any ideas as to how to use the signal mapper correctly?

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):
        print 'run'
        if identifier == 'One':
            text = 'Do This'
            print 'Do One'
        elif identifier == 'Two':
            text = 'Do That'
            print 'Do Two'
        elif identifier == 'Three':
            print 'Do 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_())

EDIT:

I've found that by using old-style signal/slot connections this is fixed:

#action.triggered.connect(self.mapper.map)
self.connect(action, QtCore.SIGNAL("triggered()"), self.mapper, QtCore.SLOT("map()"))

and

#self.mapper.mapped['QString'].connect(self.handleButton)
self.connect(self.mapper, QtCore.SIGNAL("mapped(const QString &)"), self.handleButton)

Am I using the new-style connections incorrectly?

Based on this post as well as the original link I posted, I thought I was doing things correctly.

1

There are 1 answers

1
ekhumoro On

The original example code (which I wrote), works perfectly fine for me using either Python2 or Python3 with several different recent versions of PyQt4. However, if I use a really old version of PyQt4 (4.7), the handler no longer gets called.

The reason (and solution) for this is given in the response to the mailing list post you linked to:

It's actually a problem with QSignalMapper.map() being called from a proxy rather than new-style connections.

The workaround is to explicitly specify a signal that is compatible with map()...

self.b1.clicked[()].connect(self.mapper.map)

Tonight's PyQt snapshot will be smarter about finding a usable Qt slot before deciding that it needs to use a proxy so that the workaround won't be necessary.

There are some signals (like clicked and triggered) which always send a default value unless you explicitly request otherwise. With the old-style signals, you can specify the no default overload it with SIGNAL("triggered()"), but with new-style signals, you have to do it like this:

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

But that is only necessary with very old versions of PyQt4 - the underlying issue was fixed back in 2010 (don't know the exact version, but 4.8 should be okay).