我正在开发一个Python应用程序,它从一个包含大量来源记录的大文件中绘制数据。我试图给用户的一个选项是,如果需要,只能绘制这些源的子集的选项。我通过首先阅读文件,找出有多少独特的东西,然后为每个文件创建一个QCheckBox()来命名(每个源都有一个唯一的名称)。在这种特殊情况下,数据文件被解析为一个巨大的字典,其中键是唯一的源。我想为每个复选框连接到stateChange()事件,然后在取消选中该框时禁用该源的绘图。在这种情况下,在选中/取消选中该框时,将从源列表中添加/删除源。我遇到的问题是我的所有复选框最终都连接到列表中的最终源。
最初,创建的窗口看起来正确,每个按钮都被正确命名。每次按下按钮时,btnstate()都应该只打印与该按钮关联的文本。如果您可以显式定义每个按钮,则该方法有效,如示例中的单选按钮所示。如果单击其中一个,您将获得打印按钮的正确名称,但是当取消选中/重新检查任何复选框时,btnstate将打印“test4”。
我做错了什么?
以下是代码(源更改为虚拟值):
import sys
from PyQt4.QtGui import *
def btnstate(b):
print b.text()
def main():
app = QApplication([])
widget = QWidget()
layout = QVBoxLayout()
widget.setLayout(layout)
radio_layout = QHBoxLayout()
checkbox_layout = QHBoxLayout()
#setup radio buttons for config pop-up window
r1 = QRadioButton("Page Count")
r2 = QRadioButton("Date")
r1.toggled.connect(lambda:btnstate(r1))
r2.toggled.connect(lambda:btnstate(r2))
radio_layout.addWidget(r1)
radio_layout.addWidget(r2)
cbs = []
for idx, serial in enumerate(["test1", "test2", "test3", "test4"]):
temp = QCheckBox(serial)
temp.setText(serial)
temp.setChecked(True)
checkbox_layout.addWidget(temp)
temp.stateChanged.connect(lambda:btnstate(temp))
cbs.append(temp)
layout.addLayout(radio_layout)
layout.addLayout(checkbox_layout)
widget.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
答案 0 :(得分:0)
我认为发生这种情况的原因与Python如何绑定和解除循环内的引用有关。因为temp
在每次迭代时都被重新定义,所以插槽也在被更新,因此它有效地为每个按钮调用相同的lambda。这种手工波浪,因为我对Python引用细节的理解并不是很深刻。但是我知道Python的Python绑定在Python的引用和垃圾收集方面存在很多问题,例如:删除小部件时,因为Qt对象层次结构并不完全适用于Python。
无论如何,更实际的是,有一个非常简单的解决方案。您可以使用functools.partial
方法将部分函数定义为槽,而不是lambda。将按钮绑定为第一个对象,使按钮状态(作为信号参数发出)保持未绑定状态。像这样:
import functools
def btnstate(button, state):
print button.text()
然后在循环中:
for idx, serial in enumerate(['test1', 'test2', 'test3', 'test4']):
temp = QCheckBox(serial)
checkbox_layout.addWidget(temp)
temp.stateChanged.connect(functools.partial(btnstate, serial))
运行此选项,我现在可以在选中/取消选中每个框时打印正确的标签。
编辑:
请参阅this post,了解Python的引用计数与Qt对象层次结构的奇怪方式交互的另一个例子。