使用 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())