使用 QItemDelegate 在 QTableview 中创建一列可编辑的复选框
Creating a column of editable Checkbox in QTableview using QItemDelegate
我正在使用 Pyside2,Python3.8。我有一个 QTableView,第一列的值是一个布尔值,我想实现一个委托来在第一列中绘制可编辑的复选框。我已经尝试了很多 SO 答案,最有帮助的答案是 。
它部分起作用,即:您必须双击单元格,然后在我的第一列中得到一个 'kind of' 组合框而不是复选框(见下图)
我已经设法在不通过委托的情况下做到了这一点,使用 CkeckStateRole,它起作用了,复选框不可编辑,但我有许多可编辑的其他列,似乎无法避免实施委托因为这是更合适的方法。
这是我的 Table 模特 class:
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, mlist=None):
super(TableModel, self).__init__()
self._items = [] if mlist == None else mlist
self._header = []
def rowCount(self, parent = QtCore.QModelIndex):
return len(self._items)
def columnCount(self, parent = QtCore.QModelIndex):
return len(self._header)
def flags(self, index):
if index.column() == 0:
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled
return QtCore.Qt.ItemIsEnabled
def data(self, index, role = QtCore.Qt.DisplayRole):
if not index.isValid():
return None
elif role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
return self._items[index.row()][index.column()]
elif role == QtCore.Qt.CheckStateRole:
return None
else:
return None
def setData(self, index, value, role = QtCore.Qt.EditRole):
if value is not None and role == QtCore.Qt.EditRole:
self._items[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
return False
这是我的 CheckBox 委托 class:
class CheckBoxDelegate(QtWidgets.QItemDelegate):
def __init__(self, parent = None):
QtWidgets.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
if not (QtCore.Qt.ItemIsEditable & index.flags()):
return None
check = QtWidgets.QCheckBox(parent)
check.clicked.connect(self.stateChanged)
return check
def setEditorData(self, editor, index):
editor.blockSignals(True)
editor.setChecked(index.data())
editor.blockSignals(False)
def setModelData(self, editor, model, index):
model.setData(index, editor.isChecked(), QtCore.Qt.EditRole)
def paint(self, painter, option, index):
value = index.data()
if value:
value = QtCore.Qt.Checked
else:
value = QtCore.Qt.Unchecked
self.drawCheck(painter, option, option.rect, value)
self.drawFocus(painter, option, option.rect)
@QtCore.Slot()
def stateChanged(self):
print("sender", self.sender())
self.commitData.emit(self.sender())
编辑#1
经过一些研究,我发现我忘记将 self.MyTableView.setItemDelegateForColumn(0, CheckBoxDelegate(self))
放入主窗口 __init__
好吧,现在我的第一列中有一个居中的复选框,我无法编辑它,但是,当我双击它时,它会在左侧的同一个单元格中创建第二个复选框,这是可编辑的,并且还将新的 CheckState 设置为我的模型。
假设我的居中 CheckBox 设置为 True,我双击,在其左侧创建了第二个 Checked CheckBox,我取消选中它,我单击另一个单元格,第二个 checkBox 消失,居中的 checkBox 设置为 Unchecked,我的 ModelData已更新。
(请参阅下图了解双击居中的复选框后发生的情况)
修复尝试
1.不覆盖 paint():
该列包含 True 或 False,双击该单元格会创建一个可编辑的复选框,它将模型中的数据设置为新值,然后消失。
2。 createEditor() 方法 returns None
def createEditor(self, parent, option, index):
return None
它创建一个居中的复选框,没有标签,但该复选框不可编辑
3。不覆盖 createEditor() 方法
创建一个没有标签的居中复选框,双击单元格将复选框替换为组合框(True 或 False),我可以从中更改 CheckBox 状态。
这些修复都没有给我想要的东西:创建了一个以符号为中心的复选框,只需单击一下即可编辑
完全不需要自定义委托,因为默认委托已经支持它。您只需要正确 return ItemIsUserCheckable
标志,并在 data()
和 setData()
.
中实现 CheckStateRole
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, mlist=None, checkableColumns=None):
super(TableModel, self).__init__()
self._items = [] if mlist == None else mlist
self._header = ['aaaa', 'bbb', 'ccc']
if checkableColumns is None:
checkableColumns = []
elif isinstance(checkableColumns, int):
checkableColumns = [checkableColumns]
self.checkableColumns = set(checkableColumns)
def setColumnCheckable(self, column, checkable=True):
if checkable:
self.checkableColumns.add(column)
else:
self.checkableColumns.discard(column)
self.dataChanged.emit(
self.index(0, column), self.index(self.rowCount() - 1, column))
def rowCount(self, parent = QtCore.QModelIndex):
return len(self._items)
def columnCount(self, parent = QtCore.QModelIndex):
return len(self._header)
def flags(self, index):
flags = QtCore.Qt.ItemIsEnabled
if index.column() in self.checkableColumns:
# only this flag is required
flags |= <b>QtCore.Qt.ItemIsUserCheckable</b>
return flags
def data(self, index, role = QtCore.Qt.DisplayRole):
if not index.isValid():
return None
<b>if (role == QtCore.Qt.CheckStateRole and
index.column() in self.checkableColumns):
value = self._items[index.row()][index.column()]
return QtCore.Qt.Checked if value else QtCore.Qt.Unchecked</b>
elif (index.column() not in self.checkableColumns and
role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole)):
return self._items[index.row()][index.column()]
else:
return None
def setData(self, index, value, role = QtCore.Qt.EditRole):
<b>if (role == QtCore.Qt.CheckStateRole and
index.column() in self.checkableColumns):
self._items[index.row()][index.column()] = bool(value)
self.dataChanged.emit(index, index)
return True</b>
if value is not None and role == QtCore.Qt.EditRole:
self._items[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
return False
然后,如果您想在索引没有文本时使复选框居中,您可以简单地使用代理样式:
class ProxyStyle(QtWidgets.QProxyStyle):
def subElementRect(self, element, opt, widget=None):
if element == self.SE_ItemViewItemCheckIndicator and not opt.text:
rect = super().subElementRect(element, opt, widget)
rect.moveCenter(opt.rect.center())
return rect
return super().subElementRect(element, opt, widget)
# ...
app.setStyle(ProxyStyle())
我正在使用 Pyside2,Python3.8。我有一个 QTableView,第一列的值是一个布尔值,我想实现一个委托来在第一列中绘制可编辑的复选框。我已经尝试了很多 SO 答案,最有帮助的答案是
我已经设法在不通过委托的情况下做到了这一点,使用 CkeckStateRole,它起作用了,复选框不可编辑,但我有许多可编辑的其他列,似乎无法避免实施委托因为这是更合适的方法。
这是我的 Table 模特 class:
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, mlist=None):
super(TableModel, self).__init__()
self._items = [] if mlist == None else mlist
self._header = []
def rowCount(self, parent = QtCore.QModelIndex):
return len(self._items)
def columnCount(self, parent = QtCore.QModelIndex):
return len(self._header)
def flags(self, index):
if index.column() == 0:
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled
return QtCore.Qt.ItemIsEnabled
def data(self, index, role = QtCore.Qt.DisplayRole):
if not index.isValid():
return None
elif role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
return self._items[index.row()][index.column()]
elif role == QtCore.Qt.CheckStateRole:
return None
else:
return None
def setData(self, index, value, role = QtCore.Qt.EditRole):
if value is not None and role == QtCore.Qt.EditRole:
self._items[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
return False
这是我的 CheckBox 委托 class:
class CheckBoxDelegate(QtWidgets.QItemDelegate):
def __init__(self, parent = None):
QtWidgets.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
if not (QtCore.Qt.ItemIsEditable & index.flags()):
return None
check = QtWidgets.QCheckBox(parent)
check.clicked.connect(self.stateChanged)
return check
def setEditorData(self, editor, index):
editor.blockSignals(True)
editor.setChecked(index.data())
editor.blockSignals(False)
def setModelData(self, editor, model, index):
model.setData(index, editor.isChecked(), QtCore.Qt.EditRole)
def paint(self, painter, option, index):
value = index.data()
if value:
value = QtCore.Qt.Checked
else:
value = QtCore.Qt.Unchecked
self.drawCheck(painter, option, option.rect, value)
self.drawFocus(painter, option, option.rect)
@QtCore.Slot()
def stateChanged(self):
print("sender", self.sender())
self.commitData.emit(self.sender())
编辑#1
经过一些研究,我发现我忘记将 self.MyTableView.setItemDelegateForColumn(0, CheckBoxDelegate(self))
放入主窗口 __init__
好吧,现在我的第一列中有一个居中的复选框,我无法编辑它,但是,当我双击它时,它会在左侧的同一个单元格中创建第二个复选框,这是可编辑的,并且还将新的 CheckState 设置为我的模型。 假设我的居中 CheckBox 设置为 True,我双击,在其左侧创建了第二个 Checked CheckBox,我取消选中它,我单击另一个单元格,第二个 checkBox 消失,居中的 checkBox 设置为 Unchecked,我的 ModelData已更新。 (请参阅下图了解双击居中的复选框后发生的情况)
修复尝试
1.不覆盖 paint():
该列包含 True 或 False,双击该单元格会创建一个可编辑的复选框,它将模型中的数据设置为新值,然后消失。
2。 createEditor() 方法 returns None
def createEditor(self, parent, option, index):
return None
它创建一个居中的复选框,没有标签,但该复选框不可编辑
3。不覆盖 createEditor() 方法
创建一个没有标签的居中复选框,双击单元格将复选框替换为组合框(True 或 False),我可以从中更改 CheckBox 状态。
这些修复都没有给我想要的东西:创建了一个以符号为中心的复选框,只需单击一下即可编辑
完全不需要自定义委托,因为默认委托已经支持它。您只需要正确 return ItemIsUserCheckable
标志,并在 data()
和 setData()
.
CheckStateRole
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, mlist=None, checkableColumns=None):
super(TableModel, self).__init__()
self._items = [] if mlist == None else mlist
self._header = ['aaaa', 'bbb', 'ccc']
if checkableColumns is None:
checkableColumns = []
elif isinstance(checkableColumns, int):
checkableColumns = [checkableColumns]
self.checkableColumns = set(checkableColumns)
def setColumnCheckable(self, column, checkable=True):
if checkable:
self.checkableColumns.add(column)
else:
self.checkableColumns.discard(column)
self.dataChanged.emit(
self.index(0, column), self.index(self.rowCount() - 1, column))
def rowCount(self, parent = QtCore.QModelIndex):
return len(self._items)
def columnCount(self, parent = QtCore.QModelIndex):
return len(self._header)
def flags(self, index):
flags = QtCore.Qt.ItemIsEnabled
if index.column() in self.checkableColumns:
# only this flag is required
flags |= <b>QtCore.Qt.ItemIsUserCheckable</b>
return flags
def data(self, index, role = QtCore.Qt.DisplayRole):
if not index.isValid():
return None
<b>if (role == QtCore.Qt.CheckStateRole and
index.column() in self.checkableColumns):
value = self._items[index.row()][index.column()]
return QtCore.Qt.Checked if value else QtCore.Qt.Unchecked</b>
elif (index.column() not in self.checkableColumns and
role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole)):
return self._items[index.row()][index.column()]
else:
return None
def setData(self, index, value, role = QtCore.Qt.EditRole):
<b>if (role == QtCore.Qt.CheckStateRole and
index.column() in self.checkableColumns):
self._items[index.row()][index.column()] = bool(value)
self.dataChanged.emit(index, index)
return True</b>
if value is not None and role == QtCore.Qt.EditRole:
self._items[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
return False
然后,如果您想在索引没有文本时使复选框居中,您可以简单地使用代理样式:
class ProxyStyle(QtWidgets.QProxyStyle):
def subElementRect(self, element, opt, widget=None):
if element == self.SE_ItemViewItemCheckIndicator and not opt.text:
rect = super().subElementRect(element, opt, widget)
rect.moveCenter(opt.rect.center())
return rect
return super().subElementRect(element, opt, widget)
# ...
app.setStyle(ProxyStyle())