tags:

views:

482

answers:

3

I am attempting to reduce the amount of signals I have to use in my contextmenus. The menu consists of actions which switches the operation mode of the program, so the operation carried out by the slots is very simple. Quoting the documentation on QMenu::triggered,

Normally, you connect each menu action's triggered() signal to its own custom slot, but sometimes you will want to connect several actions to a single slot, for example, when you have a group of closely related actions, such as "left justify", "center", "right justify".

However, I can't figure out how to accomplish this, and the documentation does not go into any further detail.
Suppose I have actions actionOpMode1 and actionOpMode2 in menu actionMenu, and a slot setOpMode. I want setOpMode to be called with a parameter which somehow relates to which of the actions was triggered. I tried various permutations on this theme:

    QObject.connect(self.actionMenu, SIGNAL('triggered(QAction)'), self.setOpMode)

But I never even got it to call setOpMode, which suggests that actionMenu never "feels triggered", so to speak.

In this SO question, it's suggested that it can be done with lamdbas, but this:

    QObject.connect(self.actionOpMode1, SIGNAL('triggered()'), lambda t: self.setOpMode(t))

gives "<lambda> () takes exactly 1 argument (0 given)". I can't say I really understand how this is supposed to work, so I may have done something wrong when moving from clicked() to triggered().

How is it done?

+1  A: 

You can use QObject::sender() to figure out which QAction emitted the signal.

So your slot might look like this:

def triggered(self):
    sender = QtCore.QObject.sender()

    if sender == self.actionOpMode1:
        # do something
    elif sender == self.actionOpMode2:
        # do something else

Regarding what's going on in the other SO question you mentioned with the lambda, what it does is create a lambda with one parameter which has a default value so to apply that to your example you'd need to do something like this:

self.connect(self.actionOpMode1, QtCore.SIGNAL('triggered()'), lambda who="mode1": self.changeMode(who))
self.connect(self.actionOpMode2, QtCore.SIGNAL('triggered()'), lambda who="mode2": self.changeMode(who))

And then have a member function like this:

def changeMode(self, who):
    if who == "mode1":
        # ...
    elif who == "mode2":
        # ...

Personally the first approach looks cleaner and more readable to me.

Idan K
+1  A: 

I use this approach:

from functools import partial

def bind(self, action, *params):
    self.connect(action, QtCore.SIGNAL('triggered()'), 
                 partial(action, *params, self.onMenuAction))

def onMenuAction(self, *args):
    pass


bind(self.actionOpMode1, 'action1')
bind(self.actionOpMode2, 'action2')
fabrizioM
+3  A: 

Using QObject.Sender is one of the solution, although not the cleanest one.

Use QSignalMapper to associate cleanly a value with the object that emitted the signal.

Bluebird75
+1, QSignalMapper is designed _exactly_ for this purpose (and quite elegantly, too, I think) so it offers THE architected solution.
Alex Martelli