【问题标题】:Creating a toggling "Check All" checkbox for a ListView为 ListView 创建一个切换“全选”复选框
【发布时间】:2016-02-25 06:09:21
【问题描述】:

我有一个充满可检查项目的 ListView。我想在 ListView 上方放置一个三态“全选”复选框,并且我希望这个复选框是双向的。

也就是说,如果用户切换选中所有复选框,我希望所有 ListView 的项目都反映选中所有的选择。但是,如果用户手动检查或取消选中 ListView 中的项目,我希望全选复选框反映该状态(即检查是否所有 ListView 项目都被选中,如果它们都未选中则未选中,或者如果某些 ListView项目已检查)。

This answer 显示如何连接第一部分(选中/取消选中全选框会将其状态传播到列表视图的项目)。但是,我对如何连接另一个方向感到困惑。

这就是我如何让 Check All 复选框传播到 ListView:

self.layout = QtGui.QVBoxLayout()

self.select_all_cb = QtGui.QCheckBox('Check All', self.ui.tab)
self.select_all_cb.setChecked(True)
self.select_all_cb.setStyleSheet('margin-left: 5px; font: bold')
self.select_all_cb.stateChanged.connect(self.selectAllCheckChanged)
self.layout.addWidget(select_all_cb)

self.listview = QtGui.QListView(self.ui.tab)
self.listview.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.listview.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
self.listview.setSelectionRectVisible(False)

model = QStandardItemModel()
for checkItem in self.checkItems:
    item = QStandardItem(checkItem)
    item.setCheckable(True)
    item.setSelectable(False)
    item.setCheckState(QtCore.Qt.Checked)
    model.appendRow(item)
self.listview.setModel(model)
self.layout.addWidget(listview)


def selectAllCheckChanged(self):
    model = self.listview.model()
    for index in range(model.rowCount()):
        item = model.item(index)
        if item.isCheckable():
            if self.select_all_cb.isChecked():
                item.setCheckState(QtCore.Qt.Checked)
            else:
                item.setCheckState(QtCore.Qt.Unchecked)

对如何走另一条路有什么建议吗?

【问题讨论】:

    标签: python pyqt pyqt4


    【解决方案1】:

    您可以连接到QStandardItemModel 上的itemChanged 信号并测试所有复选框的状态。

    from itertools import product
    
    self.model.itemChanged.connect(self.test_check)
    
    def test_check(self, item):
        items = [self.model.item(r,c) for r, c in product(range(self.model.rowCount()), range(self.model.columnCount())]
    
        if all(item.checkState() == Qt.Checked for item in items)
            state = Qt.Checked
        elif any(item.checkState() == Qt.Checked for item in items):
            state = Qt.PartiallyChecked
        else:
            state = Qt.Unchecked
    
        if self.select_all_cb.checkState() != state:
            self.select_all_cb.setCheckState(state)
    

    如果您有非常多的复选框,您可以通过缓存每个项目的检查状态并在项目状态更改时更新缓存,然后检查缓存而不是从每个项目中提取缓存来优化这一点每次。

    如果您知道要一次对多个项目进行更改,您可能应该在模型上阻止信号,然后在进行所有更改后手动运行此函数。

    在您的 selectAllCheckChanged 处理程序中,您还应该阻止模型上的信号,使其不会触发此处理程序

    def selectAllCheckChanged(self):
        model = self.listview.model()
        model.blockSignals(True)
        try:
            for index in range(model.rowCount()):
                item = model.item(index)
                if item.isCheckable():
                    if self.select_all_cb.isChecked():
                        item.setCheckState(QtCore.Qt.Checked)
                    else:
                        item.setCheckState(QtCore.Qt.Unchecked)
        finally:
            model.blockSignals(False)
    

    【讨论】:

    • 谢谢,这很好用。唯一的问题是全选复选框在部分状态中循环。我通过在复选框被选中或未选中时调用.setTristate(False) 来解决这个问题,并且只有在需要将其设置为部分选中状态时才调用.setTristate(True)
    【解决方案2】:

    如果它可以帮助其他人,我将 Brendan 的答案合并到我的代码中。不同之处在于仅在需要时才启用三态功能(因此用户无法启用部分检查状态),并且我将其与clicked 信号而不是stateChange 连接以避免selectAllCheckChanged 被触发listviewCheckChanged。当然,model.blockSignals 也可以使用,但在我看来,使用 clicked 似乎更符合 Python 风格。

    self.layout = QtGui.QVBoxLayout()
    
    self.select_all_cb = QtGui.QCheckBox('Check All', self.ui.tab)
    self.select_all_cb.setTristate(False) # Only enable tristate when necessary so the user doesn't click it through to partially checked
    self.select_all_cb.setChecked(True)
    self.select_all_cb.setStyleSheet('margin-left: 5px; font: bold')
    self.select_all_cb.clicked.connect(self.selectAllCheckChanged) # clicked instead of stateChanged so this doesn't get triggered by ListView's changes
    self.layout.addWidget(select_all_cb)
    
    self.listview = QtGui.QListView(self.ui.tab)
    self.listview.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
    self.listview.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
    self.listview.setSelectionRectVisible(False)
    
    model = QStandardItemModel()
    for checkItem in self.checkItems:
        item = QStandardItem(checkItem)
        item.setCheckable(True)
        item.setSelectable(False)
        item.setCheckState(QtCore.Qt.Checked)
        model.appendRow(item)
    self.listview.setModel(model)
    self.listview.clicked.connect(self.listviewCheckChanged)
    self.layout.addWidget(listview)
    
    
    def selectAllCheckChanged(self):
        ''' updates the listview based on select all checkbox '''
        model = self.listview.model()
        for index in range(model.rowCount()):
            item = model.item(index)
            if item.isCheckable():
                if self.select_all_cb.isChecked():
                    item.setCheckState(QtCore.Qt.Checked)
                else:
                    item.setCheckState(QtCore.Qt.Unchecked)
    
    def listviewCheckChanged(self):
        ''' updates the select all checkbox based on the listview '''
        model = self.listview.model()
        items = [model.item(index) for index in range(model.rowCount())]
    
        if all(item.checkState() == QtCore.Qt.Checked for item in items):
            self.select_all_cb.setTristate(False)
            self.select_all_cb.setCheckState(QtCore.Qt.Checked)
        elif any(item.checkState() == QtCore.Qt.Checked for item in items):
            self.select_all_cb.setTristate(True)
            self.select_all_cb.setCheckState(QtCore.Qt.PartiallyChecked)
        else:
            self.select_all_cb.setTristate(False)
            self.select_all_cb.setCheckState(QtCore.Qt.Unchecked)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-13
      • 2011-09-09
      • 1970-01-01
      • 2013-04-03
      • 1970-01-01
      • 2015-12-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多