PyQt - 如何将多个信号连接到同一个小部件

Posted

技术标签:

【中文标题】PyQt - 如何将多个信号连接到同一个小部件【英文标题】:PyQt - How to connect multiple signals to the same widget 【发布时间】:2012-09-11 19:02:45 【问题描述】:
[ ]All1           [ ]All2        

[ ]checkbox1A     [ ]checkbox1B

[ ]checkbox2A     [ ]checkbox2B

根据上面的图表,需要做一些事情:

    所有复选框仅影响其所在列的开/关,并检查该列中所有复选框的开/关。 所有复选框都成对工作,因此如果 checkbox1A 开启/关闭,则 checkbox1B 需要开启/关闭 如果选中了“全部”复选框,然后用户继续选中该列中的一个或多个复选框,则“全部”复选框应取消选中,但所有已选中的复选框应保持选中状态。

所以实际上这更像是连锁反应设置。如果复选框 All1 处于打开状态,则 chieckbox1A 和 2A 将处于打开状态,并且因为它们处于打开状态,所以 checkbox1B 和 2B 也处于打开状态,但复选框 All2 保持关闭状态。我尝试根据此逻辑连接信号,但只有配对逻辑 100% 有效。 All 复选框逻辑仅在 50% 的情况下有效,而且不准确,如果不关闭所有已选中的复选框,我无法关闭 All 复选框。

真的很需要帮助……T-T

示例代码:

cbPairKeys = cbPairs.keys()
    for key in cbPairKeys:
        cbOne = cbPairs[key][0][0]
        cbTwo = cbPairs[key][1][0]
        cbOne.stateChanged.connect(self.syncCB)
        cbTwo.stateChanged.connect(self.syncCB)

def syncCB(self):               
    pairKeys = cbPairs.keys()
    for keys in pairKeys:
        cbOne = cbPairs[keys][0][0]
        cbOneAllCB = cbPairs[keys][0][4]
        cbTwo = cbPairs[keys][1][0]
        cbTwoAllCB = cbPairs[keys][1][4]

        if self.sender() == cbOne:
            if cbOne.isChecked() or cbTwoAllCB.isChecked():
                cbTwo.setChecked(True)
            else:
                cbTwo.setChecked(False)
        else:
            if cbTwo.isChecked() or cbOneAllCB.isChecked():
                cbOne.setChecked(True)
            else:
                cbOne.setChecked(False) 

编辑

感谢用户 Avaris 的帮助和耐心,我能够将代码简化为更简洁的代码,并且 100% 的时间可以在第一个和第二个期望的行为上工作:

#Connect checkbox pairs     
cbPairKeys = cbPairs.keys()
    for key in cbPairKeys:
        cbOne = cbPairs[key][0][0]
        cbTwo = cbPairs[key][1][0]
        cbOne.toggled.connect(cbTwo.setChecked)
        cbTwo.toggled.connect(cbOne.setChecked) 

#Connect allCB and allRO signals    
cbsKeys = allCBList.keys()
    for keys in cbsKeys:
        for checkbox in allCBList[keys]:
            keys.toggled.connect(checkbox.setChecked)

当用户现在有选择地关闭模块化复选框时,只需要关闭“全部”复选框的帮助

【问题讨论】:

你的代码在哪里?您应该提供一个小的、独立的示例来说明问题。 【参考方案1】:

如果我了解您的数据结构,我有一个解决方案。如果我错了,请纠正我:allCBListdict(名称混淆!:))。它的键是all* 复选框。而allCBList[key] 的值是一个list,其复选框与all 复选框相关联。对于您的示例结构,它将是这样的:

 All1 : [checkbox1A, checkbox1B],
  All2 : [checkbox2A, checkbox2B]

那么你需要做的是:当一个复选框被切换并且它处于checked状态时,如果所有其他复选框都处于checked状态,那么你需要检查All*复选框。否则将被取消选中。

for key, checkboxes in allCBList.iteritems():
    for checkbox in checkboxes:
        checkbox.toggled.connect(lambda checked, checkboxes=checkboxes, key=key: key.setChecked(checked and all(checkbox.isChecked() for checkbox in checkboxes))

我猜,这个说法需要一点解释:

lambda checked, checkboxes=checkboxes, key=key:

lambda 创建连接到信号的可调用对象。 toggled 传递复选框状态,它将传递给checked 变量。 checkboxes=checkboxeskey=key 部分将当前值传递给 lambda 的 checkboxeskey 参数。 (因为lambdas 的关闭,你需要这个)

接下来是:

key.setChecked(...)

我们正在设置keychecked 状态,这是适当的All* 复选框。在这里面:

checked and all(checkbox.isChecked() for checkbox in checkboxes)

allTrue,如果里面的所有内容都是 True,我们检查每个 checkboxs 状态。如果所有都是checked,这将返回True(即isChecked()返回True)。

checked and ... 部分用于短路all。如果当前复选框变成unchecked,那么我们就不需要勾选其他的了。 All* 将是 unchecked

(PS:顺便说一句,你不需要得到dict.keys() 来迭代键。你可以只迭代dict,它会迭代超过其keys。)

编辑:为了避免通过单击任何子复选框切换All* 复选框的连锁反应,有必要将All* 复选框的信号更改为clicked,而不是@987654366 @。因此,All* 复选框在用户交互的情况下会影响它们下方的其他复选框。

最后,您修改后的代码将是:

# Connect checkbox pairs
# you just use the values
# change 'itervalues' to 'values' if you are on Python 3.x
for cbPair in cbPairs.itervalues():
    cbOne = cbPair[0][0]
    cbTwo = cbPair[1][0]
    cbOne.toggled.connect(cbTwo.setChecked)
    cbTwo.toggled.connect(cbOne.setChecked) 

# Connect allCB and allRO signals
# change 'iteritems' to 'items' if you are on Python 3.x
for key, checkboxes in allCBList.iteritems():
    for checkbox in checkboxes:
        key.clicked.connect(checkbox.setChecked)
        checkbox.toggled.connect(lambda checked, checkboxes=checkboxes, key=key: key.setChecked(checked and all(checkbox.isChecked() for checkbox in checkboxes))

【讨论】:

所以我尝试了这种方法,当一个子复选框被选中时,All* 复选框也被选中,但连锁反应也发生在其他仍然选中的子复选框上。在 All* 复选框被选中后,仍然选中的子复选框都被选中,在 All* 复选框之后 我也想感谢你在这个问题上帮助我。如果没有您的耐心和有用的提示和技巧,该工具永远不会走到这一步。非常感谢! @Orchainu:哦,对了! :),忘记了。修复很容易。我们只需要使All* 复选框仅通过用户操作影响其他人。那将是clicked 信号而不是toggled。我要编辑我的答案。【参考方案2】:

您的问题是您的复选框正在连接toggled 信号在您连接的插槽中切换它们的状态,以便再次发出信号(因此插槽再次执行......)并且您得到不可预知的结果。显然这不是你想要的行为。您可以通过多种方式修复它:

通过断开插槽开头的信号并在末尾重新连接它们 通过使用一些巧妙的代码来控制信号的重新发射(我认为这是 Avari 的代码以非常紧凑的方式所做的事情,但我并不完全确定) 使用clicked 信号,因为当复选框状态改变时它不会重新发出

您采用哪种方法取决于您自己。以下代码使用第三种方式:

    self.cbPair = 
    self.cbPair['0'] = (QtGui.QCheckBox('all1', parent), 
        QtGui.QCheckBox('all2', parent))
    self.cbPair['1'] = (QtGui.QCheckBox('1a', parent), 
        QtGui.QCheckBox('1b', parent))
    self.cbPair['2'] = (QtGui.QCheckBox('2a', parent), 
        QtGui.QCheckBox('2b', parent))

    for v in self.cbPair.values():
        for cb in v:
            cb.clicked.connect(self.updateCB)

def updateCB(self):
    cb = self.sender()
    is_checked = cb.isChecked()
    id = str(cb.text())
    try:
        # Update a whole column
        column = int(id[-1]) - 1
        rows = ('1', '2')
    except ValueError:
        # Update a row and the headers row
        rows = (id[0], )
        column = 'a': 1, 'b': 0.get(id[-1])
        if not is_checked:
            for c in (0, 1):
                self.cbPair['0'][c].setChecked(is_checked)
    for r in rows:
        self.cbPair[r][column].setChecked(is_checked)

请注意,我使用复选框文本作为 UID,从哪一行计算列值。如果您想为您的复选框使用不同的文本标签,您可能需要将 UID 设置为每个复选框的属性。

【讨论】:

以上是关于PyQt - 如何将多个信号连接到同一个小部件的主要内容,如果未能解决你的问题,请参考以下文章

将插槽连接到来自类派生的所有对象的信号

如何将小部件连接到在一个类中声明的自定义信号,同时在另一个类中

PyQt:计算太长时发出两次信号

如何隐藏在 qt 中生成信号的小部件

每个选项卡小部件的 PyQt 选项

如何将函数连接到主线程外的 PyQt 信号