我有一个充满可检查项目的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
以避免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)