PyQt : Connecter plusieurs signaux à un même slot dans une boucle

Supposons qu'on ait un ensemble de QCheckBox nommées checkBox1, checkBox2, etc, jusqu'à checkBox10 par exemple.

On souhaite déclencher une action lorsque l'utilisateur coche ou décoche ces QCheckBox.
Pour cela, on va faire un stateChanged.connect() dans une boucle :

Note : les exemples de code de cet article sont extraits du code d'une fenêtre. self désigne donc un objet qui hérite de QDialog.

for i in range(1, 11):
    checkBox = self.findChild(QCheckBox, 'checkBox' + str(i))
    checkBox.stateChanged.connect(self.checkBoxStateChanged)
def checkBoxStateChanged(self, state):
    print state

Cela fonctionne, mais, problème : on ne sait pas laquelle des checkbox a été cochée/décochée.

 

Solution 1 : récupérer l'objet QCheckBox qui a déclenché le signal, grâce à la méthode sender() de la classe QObject

def checkBoxStateChanged(self, state):
    senderCheckBox = self.sender()
    nom = senderCheckBox.objectName()
    i = int(nom[8:])
    print i, state

Dans le cas présent, on s'est basé sur le nom des QCheckBox.

Mais dans certains cas, ce n'est pas possible.
On peut par exemple imaginer avoir placé des QCheckBox dans chaque ligne d'un QTableWidget, sans leur avoir défini de nom.
Pour déterminer la QCheckBox à l'origine du signal, on peut alors parcourir les QCheckBox susceptibles d'avoir déclenché le signal, et les comparer à senderCheckBox :

def checkBoxStateChanged(self, state):
    senderCheckBox = self.sender()
    tableWidget = self.dlg.findChild(QTableWidget, "tableWidget")
    rowCount = tableWidget.rowCount()
    for i in range(0, rowCount):
        checkbox = tableWidget.cellWidget(i, 0) # ligne i, colonne 0
        if checkbox == senderCheckbox:
            # On a trouvé la checkbox à l'origine du signal
            print i, state
            break # On sort de la boucle for

 

Solution 2 : Utiliser un paramètre indiquant quelle checkbox a changé d'état

On modifie notre méthode checkBoxStateChanged() pour ne plus prendre l'état en paramètre, mais le numéro de la QCheckBox qui a été cochée/décochée :

def checkBoxStateChanged(self, i):
    checkBox = self.findChild(QCheckBox, 'checkBox' + str(i))
    print i, checkBox.isChecked()

Bien sûr, il faut modifier l'appel à connect() :

Mais attention, l'appel suivant ne fonctionnera pas :

for i in range(1, 11):
    checkBox = self.findChild(QCheckBox, 'checkBox' + str(i))
    checkBox.stateChanged.connect(lambda: self.checkBoxStateChanged(i))

Cela passera systématiquement 10 comme paramètre de la méthode, car la valeur de i passée en paramètre sera celle au moment du déclenchement du signal, et non pas celle au moment de l'exécution de la boucle.

lambda n'est donc pas la bonne solution. Mais on s'en rapproche.

En réalité, il faut utiliser functools.partial :

from functools import partial
for i in range(1, 11):
    checkBox = self.findChild(QCheckBox, 'checkBox' + str(i))
    checkBox.stateChanged.connect(partial(self.checkBoxStateChanged, i))

Cela fonctionnera comme attendu.

Pour plus d'informations :

Tags :
Python
Qt
PyQt
Signaux
Slots