The proper use of QSignalMapper

B

borntonetwork

Hi.

I am trying to implement QTCore.QSignalMapper using PyQT. I finally got
to a point where I don't receive any compile or runtime error messages,
but I also do not see the final slot function fire off. Here is a
snippet of the code:

self.signalMapper = QtCore.QSignalMapper(window)
# Use qsignalmapper to use of one slot function for multiple
# widgets. The map() function sends the slot an extra param
# that identifies the sender.
for idx in range(1, maxIngredients+1):
wName = 'chkProductIngredientsDelete_'+str(idx)
w = self.__dict__[wName]
self.app.connect(w, QtCore.SIGNAL("stateChanged(int)"),
self.signalMapper,
QtCore.SLOT("self.signalMapper.map"))
self.signalMapper.setMapping(w, idx)

self.app.connect(self.signalMapper, QtCore.SIGNAL(
"self.signalMapper.mapped(int)"),
self.deleteProductIngredient)

def deleteProductIngredient(self, state, widgetIdx):
"""Delete a product ingredient."""
print "INSIDE FUNCTION\n"

The idea here is to iterate through several checkbox widgets (named
"chkProductIngredientsDelete_1, _2, etc.) and connect each object's
"stateChanged" signal to the mapper's map() function, then connect the
map() function to the actual slot, deleteProductIngredient().

By the way, I've checked to ensure the statements inside the loop are
being executed.
 
D

David Boddie

borntonetwork said:
I am trying to implement QTCore.QSignalMapper using PyQT. I finally got
to a point where I don't receive any compile or runtime error messages,
but I also do not see the final slot function fire off. Here is a
snippet of the code:

self.signalMapper = QtCore.QSignalMapper(window)
# Use qsignalmapper to use of one slot function for multiple
# widgets. The map() function sends the slot an extra param
# that identifies the sender.
for idx in range(1, maxIngredients+1):
wName = 'chkProductIngredientsDelete_'+str(idx)
w = self.__dict__[wName]
self.app.connect(w, QtCore.SIGNAL("stateChanged(int)"),
self.signalMapper,
QtCore.SLOT("self.signalMapper.map"))

You need to pass the C++ signature to the SLOT() function. I believe
you want the form that does not accept any arguments:

self.app.connect(w, QtCore.SIGNAL("stateChanged(int)"),
self.signalMapper,
QtCore.SLOT("map()"))

Alternatively, you can pass the slot directly to the function:

self.app.connect(w, QtCore.SIGNAL("stateChanged(int)"),
self.signalMapper.map)

PyQt accepts Python methods as slots, without requiring that they be
wrapped in calls to SLOT().

David
 
B

borntonetwork

Thanks, David, for you help.

When I change the slot function to what you show in your second
example, I get the same results: nothing. When I change it to what you
have in your first example, I get the following:

Object::connect: No such slot QApplication::map()
Object::connect: (sender name: 'chkProductIngredientsDelete_1')
Object::connect: (receiver name: 'main.py')
Object::connect: No such slot QApplication::map()
Object::connect: (sender name: 'chkProductIngredientsDelete_2')
Object::connect: (receiver name: 'main.py')
Object::connect: No such slot QApplication::map()
Object::connect: (sender name: 'chkProductIngredientsDelete_3')
Object::connect: (receiver name: 'main.py')
Object::connect: No such slot QApplication::map()
Object::connect: (sender name: 'chkProductIngredientsDelete_4')
Object::connect: (receiver name: 'main.py')
Object::connect: No such slot QApplication::map()
Object::connect: (sender name: 'chkProductIngredientsDelete_5')
Object::connect: (receiver name: 'main.py')
Object::connect: No such slot QApplication::map()
Object::connect: (sender name: 'chkProductIngredientsDelete_6')
Object::connect: (receiver name: 'main.py')


David said:
borntonetwork said:
I am trying to implement QTCore.QSignalMapper using PyQT. I finally got
to a point where I don't receive any compile or runtime error messages,
but I also do not see the final slot function fire off. Here is a
snippet of the code:

self.signalMapper = QtCore.QSignalMapper(window)
# Use qsignalmapper to use of one slot function for multiple
# widgets. The map() function sends the slot an extra param
# that identifies the sender.
for idx in range(1, maxIngredients+1):
wName = 'chkProductIngredientsDelete_'+str(idx)
w = self.__dict__[wName]
self.app.connect(w, QtCore.SIGNAL("stateChanged(int)"),
self.signalMapper,
QtCore.SLOT("self.signalMapper.map"))

You need to pass the C++ signature to the SLOT() function. I believe
you want the form that does not accept any arguments:

self.app.connect(w, QtCore.SIGNAL("stateChanged(int)"),
self.signalMapper,
QtCore.SLOT("map()"))

Alternatively, you can pass the slot directly to the function:

self.app.connect(w, QtCore.SIGNAL("stateChanged(int)"),
self.signalMapper.map)

PyQt accepts Python methods as slots, without requiring that they be
wrapped in calls to SLOT().

David
 
D

David Boddie

borntonetwork said:
Thanks, David, for you help.

When I change the slot function to what you show in your second
example, I get the same results: nothing.

This may be due to something I missed in your code. When you
connect the signal from the signal mapper to your class, you
need to specify the signal as a C++ signature as well:

self.connect(self.signalMapper, QtCore.SIGNAL(
"mapped(int)"),
self.deleteProductIngredient)

The second example should now work.

Looking at the first example, which uses SLOT() rather than specifying
a Python method, the following might be due to the way you call
connect, though it is surprising:
When I change it to what you
have in your first example, I get the following:

Object::connect: No such slot QApplication::map()
Object::connect: (sender name: 'chkProductIngredientsDelete_1')
Object::connect: (receiver name: 'main.py')

[...]

It looks like you should try something like this:

self.connect(w, QtCore.SIGNAL("stateChanged(int)"),
self.signalMapper,
QtCore.SLOT("map()"))

I can't understand why calling self.app.connect() would cause the
connection to be attempted between self.signalMapper and self.app.

Maybe someone on the PyQt/PyKDE mailing list would be able to
explain the behaviour you're seeing:

http://mats.imk.fraunhofer.de/mailman/listinfo/pykde

David
 
B

borntonetwork

David, thanks for your help. Unfortunately, all attempts of making this
solution work have failed. I would be interested to know if anyone has
used QSignalMapper successfully in a similar situation. For any
interested, I worked around the problem using a closure, which seems a
bit cleaner from a coding point of view, although I don't have any idea
if it is more efficient or not:

def makeHandler(self, idx):
def handler(state):
print 'The state is:', str(state), 'for checkbox object',
str(idx)
return handler

Each iteration then created a new function based on the closure:

self.handlers = []
for idx in range(1, maxIngredients+1):
wName = 'chkProductIngredientsDelete_'+str(idx)
w = self.__dict__[wName]
self.handlers.append(self.makeHandler(idx - 1))
self.window.connect(w, QtCore.SIGNAL("stateChanged(int)"),
self.handlers[idx - 1])

So now the output when each checkbox is clicked is something like this:

The state is: 2 for checkbox object 0
The state is: 2 for checkbox object 1
....



David said:
borntonetwork said:
Thanks, David, for you help.

When I change the slot function to what you show in your second
example, I get the same results: nothing.

This may be due to something I missed in your code. When you
connect the signal from the signal mapper to your class, you
need to specify the signal as a C++ signature as well:

self.connect(self.signalMapper, QtCore.SIGNAL(
"mapped(int)"),
self.deleteProductIngredient)

The second example should now work.

Looking at the first example, which uses SLOT() rather than specifying
a Python method, the following might be due to the way you call
connect, though it is surprising:
When I change it to what you
have in your first example, I get the following:

Object::connect: No such slot QApplication::map()
Object::connect: (sender name: 'chkProductIngredientsDelete_1')
Object::connect: (receiver name: 'main.py')

[...]

It looks like you should try something like this:

self.connect(w, QtCore.SIGNAL("stateChanged(int)"),
self.signalMapper,
QtCore.SLOT("map()"))

I can't understand why calling self.app.connect() would cause the
connection to be attempted between self.signalMapper and self.app.

Maybe someone on the PyQt/PyKDE mailing list would be able to
explain the behaviour you're seeing:

http://mats.imk.fraunhofer.de/mailman/listinfo/pykde

David
 
D

David Boddie

David, thanks for your help. Unfortunately, all attempts of making this
solution work have failed. I would be interested to know if anyone has
used QSignalMapper successfully in a similar situation.

Looking again at what you originally wrote, and looking at your
solution below, I don't think it's really appropriate to use a
QSignalMapper for this purpose. (That's what I understood from the
signature of the deleteProductIngredient() method you had in your
original code.)

What you appear to want is a way of being notified about state
changes to widgets that also identifies the individual widget
that changed. You can do that by connecting the individual widgets
to the same slot and call sender() to find out which object emitted
the signal; for example:

# ...

        for idx in range(1, maxIngredients+1):
            wName = 'chkProductIngredientsDelete_'+str(idx)
            w = self.__dict__[wName]
            self.connect(w, QtCore.SIGNAL("stateChanged(int)"),
             self.deleteProductIngredient)

def deleteProductIngredient(self, state):
"""Delete a product ingredient."""
print self.sender(), "changed state to", state

What QSignalMapper does is collect signals from different objects,
assigning an ID to each of them, and emits a single signal with
an ID whenever it receives a signal from one of those objects.
In other words, it allows the actions of a group of objects to be
described by a parameter but discards the signal's arguments.
It's most useful for things like collections of push buttons where
you only need to know that they were clicked.
For any interested, I worked around the problem using a closure,
which seems a bit cleaner from a coding point of view, although
I don't have any idea if it is more efficient or not:
[...]

Each iteration then created a new function based on the closure:

[...]

It's good to know that you got something working in the end. There's
also another solution that involves QSignalMapper, but it may not make
the resulting code any simpler.

David
 
B

borntonetwork

How simple. I will remember that sender() function for future
reference.
Thanks, David.

David said:
David, thanks for your help. Unfortunately, all attempts of making this
solution work have failed. I would be interested to know if anyone has
used QSignalMapper successfully in a similar situation.

Looking again at what you originally wrote, and looking at your
solution below, I don't think it's really appropriate to use a
QSignalMapper for this purpose. (That's what I understood from the
signature of the deleteProductIngredient() method you had in your
original code.)

What you appear to want is a way of being notified about state
changes to widgets that also identifies the individual widget
that changed. You can do that by connecting the individual widgets
to the same slot and call sender() to find out which object emitted
the signal; for example:

# ...

for idx in range(1, maxIngredients+1):
wName = 'chkProductIngredientsDelete_'+str(idx)
w = self.__dict__[wName]
self.connect(w, QtCore.SIGNAL("stateChanged(int)"),
self.deleteProductIngredient)

def deleteProductIngredient(self, state):
"""Delete a product ingredient."""
print self.sender(), "changed state to", state

What QSignalMapper does is collect signals from different objects,
assigning an ID to each of them, and emits a single signal with
an ID whenever it receives a signal from one of those objects.
In other words, it allows the actions of a group of objects to be
described by a parameter but discards the signal's arguments.
It's most useful for things like collections of push buttons where
you only need to know that they were clicked.
For any interested, I worked around the problem using a closure,
which seems a bit cleaner from a coding point of view, although
I don't have any idea if it is more efficient or not:
[...]

Each iteration then created a new function based on the closure:

[...]

It's good to know that you got something working in the end. There's
also another solution that involves QSignalMapper, but it may not make
the resulting code any simpler.

David
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,436
Messages
2,571,696
Members
48,796
Latest member
Greg L.
Top