为列表视图创建切换"Check All"复选框



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

也就是说,如果用户切换"全选"复选框,我希望 ListView 的所有项都镜像全选中项的选择。但是,如果用户手动选中或取消选中 ListView 中的项目,我希望全选复选框反映该状态(即

,如果选中所有 ListView 项目,则选中,如果全部未选中则取消选中,则取消选中,如果选中某些 ListView 项目,则部分选中)。

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

这就是我让"全选中"复选框传播到 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)

关于如何走另一条路的任何建议?

您可以连接到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)

如果它能帮助其他人,以下是我将Brendan的答案纳入我的代码的方式。不同之处在于三态功能仅在需要时启用(因此用户无法启用部分检查状态),并且我将其与clicked信号连接而不是stateChange以避免selectAllCheckChangedlistviewCheckChanged触发。当然,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)

最新更新