PyQt4正确连接QCheckbox状态

3
我有一个类QWidget,其中包含一个QLabel。该类在QVBox中生成QCheckbox。我试图将每个复选框连接到方法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

我找到了几个“hack”解决方案。

解决方案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] ) )

然而,我想在一行中使用Lambda表达式。 特别是我想了解它是如何工作的。 通过以下链接,我理解了问题与Lambda范围有关。 正如Oleh Prypin所建议的那样,我写了:

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变量似乎被视为一个整数?这里会发生什么?

1个回答

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

将lambda函数绑定到变量i,这意味着在调用lambda函数时,变量i的值将是此时的值,而不是创建lambda函数时的值,即此处始终为9。
可能的解决方法:

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

这个主题有很多通用信息。起点:谷歌搜索,另一个问题在循环内创建lambda
不幸的是,我的修复方法没有生效,因为该信号向调用的函数提供了一个参数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())

谢谢,我已经使用回调函数的解决方案编辑了我的帖子,并按照您所写的语法进行了修改,但仍然无法正常工作,也许listCB的范围也有问题? - kaligne
非常感谢,两种方法都非常好用。作为新手,我往往会忘记 sender() 方法,这证明它非常有用。我想我需要系统地检查文档中信号返回的值? - kaligne
这个(lambda checked, i=i: self.nameCheckBox(label, listCB[i]))对我来说完美运作了,谢谢! - Nicholas Barrow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接