pyqt4 правильно подключает состояние QCheckbox

У меня есть класс класса QWidget, содержащий класс QLabel. Класс генерирует QCheckbox в QVBox. Я пытаюсь подключить каждый флажок к методу nameCheckBox, который обновит QLabel, чтобы отобразить заголовок последнего отмеченного поля. Однако, когда флажок фактически снят/установлен, он всегда определяется как снятый. Также возвращаемое имя всегда является последним созданным флажком. Я не понимаю, где моя ошибка. Вот мой код:

import sys
from PyQt4 import QtCore
from PyQt4.QtGui import *
from MenusAndToolbars import MenuWindow

class checkBoxWidget(QWidget):
    """
    This widget has a QVBox which contains a QLabel and QCheckboxes.
    Qcheckbox number is connected to the label.
    """

    def __init__(self):
        QWidget.__init__(self)
        self.__setUI()

    def __setUI(self):
        vbox = QVBoxLayout(self)
        label = QLabel('Last clicked button: ' + "None", self)

        vbox.addWidget(label)

        listCB = []

        for i in range(10):
            listCB.append( QCheckBox('CheckBox Nb. ' + str(i+1) ) )
            listCB[i].stateChanged.connect(lambda: self.nameCheckBox(label,  listCB[i]) )
            vbox.addWidget( listCB[i] )


    def nameCheckBox(self, label, checkBox):
        if checkBox.isChecked():
            print "Checked: " + checkBox.text()
            label.setText('Last clicked button: ' + checkBox.text())
        else:
            print "Unchecked: " + checkBox.text()



def main():
    app = QApplication(sys.argv)
    window = QMainWindow()
    window.setCentralWidget( checkBoxWidget() )
    window.show()
    #window = WidgetWindow()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

РЕДАКТИРОВАТЬ 1

Я нашел несколько «хакерских» решений.

РЕШЕНИЕ 1. Создание функции обратного вызова помогает:

def callBack(self, list, index, label):
    return lambda: self.nameCheckBox(label, list[index])

Затем я подключаю сигнал QCheckbox().stateChanged таким образом:

listCB[i].stateChanged.connect( self.callBack(listCB, i, label) )

РЕШЕНИЕ 2: с помощью модуля partial:

Сначала мы импортируем модуль:

from functools import partial

Тогда подключение сигнала делается так:

listCB[i].stateChanged.connect( partial( self.nameCheckBox, label, listCB[i] ) )

Однако я хотел бы использовать лямбда-выражение в одной строке. Особенно хотелось бы понять, как это работает. По ссылкам я понял, что проблема связана с областью лямбда. Как мне посоветовал Олег Припин, я написал:

listCB[i].stateChanged.connect(lambda i=i: self.nameCheckBox(label,  listCB[i]) )

Здесь переменная i является новой. Однако моя первоначальная проблема остается. Затем я попробовал это из любопытства:

listCB[i].stateChanged.connect( lambda label=label, listCB=listCB, i=i: self.nameCheckBox(label, listCB[i] ) )

Но я получаю следующую ошибку:

Traceback (most recent call last):
Checked: CheckBox Nb. 2
  File "Widgets.py", line 48, in <lambda>
    listCB[i].stateChanged.connect( lambda label=label, listCB=listCB, i=i: self.nameCheckBox(label, listCB[i] ) )
  File "Widgets.py", line 59, in nameCheckBox
    label.setText('Last clicked button: ' + checkBox.text())
AttributeError: 'int' object has no attribute 'setText'

Здесь кажется, что правильная кнопка распознается, когда она не отмечена. Однако кажется, что новая переменная label рассматривается как int? Что здесь происходит?


person kaligne    schedule 24.12.2014    source источник


Ответы (1)


lambda: self.nameCheckBox(label,  listCB[i])

привязывается к переменной i, что означает, что значение i будет на момент вызова лямбды, а не при ее создании, что в данном случае всегда равно 9.
Возможное исправление:

lambda i=i: self.nameCheckBox(label, listCB[i])

На эту тему есть много общей информации. Отправная точка: поиск Google, еще один вопрос Создание лямбда внутри цикла.


К сожалению, мое исправление не сработало, потому что этот сигнал предоставляет аргумент checked вызываемой функции, переопределяя этот аргумент по умолчанию на 0 или 2 в зависимости от состояния проверки. Это будет работать (игнорируйте нежелательный аргумент):

lambda checked, i=i: self.nameCheckBox(label, listCB[i])

И вот альтернативный способ написать этот класс:

class CheckBoxWidget(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        self.setupUi()

    def setupUi(self):
        vbox = QVBoxLayout(self)
        self.label = QLabel("Last clicked button: None")

        vbox.addWidget(self.label)

        for i in range(10):
            cb = QCheckBox("CheckBox Nb. " + str(i+1))
            cb.stateChanged.connect(self.nameCheckBox)
            vbox.addWidget(cb)

    def nameCheckBox(self, checked):
        checkBox = self.sender()
        if checked:
            print("Checked: " + checkBox.text())
            self.label.setText("Last clicked button: " + checkBox.text())
        else:
            print("Unchecked: " + checkBox.text())
person Oleh Prypin    schedule 24.12.2014
comment
Спасибо, я отредактировал свой пост с решением для обратного вызова и синтаксисом, который я должен использовать, как вы написали, однако он все еще не работает, может быть, есть проблема с областью действия listCB? - person kaligne; 24.12.2014
comment
Большое спасибо, оба метода работают как шарм. Будучи новичком, я склонен забывать метод sender(), который оказывается полезным. Я предполагаю, что мне нужно систематически проверять значения, возвращаемые сигналами, в документации? - person kaligne; 24.12.2014