Creating a column of editable Checkbox in QTableview using QItemDelegate
-
I'm using Pyside2, Python 3.8. I have a QTableView, the value of the first column is a bool, I want to implement a delegate to paint Editable CheckBoxs in the first column.
I get a centered CheckBox in my first column, I can't edit it, but, when I double click it, It creates a second Checkbox in the same cell at the left, which is Editable and also sets the new CheckState to my Model. Let's say my centered CheckBox is set to True, I double click, a second Checked CheckBox is created to its left, I Uncheck it, I click somewhere else, the second checkBox disappears, the centered checkBox is set to Unchecked, and my ModelData is updated. (see picture below for what happens after double clicking the centered CheckBox)Here's my Table Model 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
My CheckBox Delegate 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): print('set model data', editor.isChecked()) model.setData(index, editor.isChecked(), QtCore.Qt.EditRole) def paint(self, painter, option, index): print('painting', index.data()) 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())
In My MainWindow Class, I added the following line:
self.MyTableView.setItemDelegateForColumn(0, CheckBoxDelegate(self))
Any thoughts on how to resolve this ?
Thanks in advance.
-
I've tried different fixes:
- Not overriding paint():
The column contains either True or false, double clicking the cell creates an editable checkbox
- the createEditor() method returns None
def createEditor(self, parent, option, index): return None
It creates a single centered checkbox, no labels, but the checkbox isn't editable
- Not overriding createEditor() method
Creates a single centered checkbox with no labels, double clicking the cell replace the checkbox with a combobox (True or False), from which I can change the CheckBox state.
Neither of those fixes gave me what I'm looking for: A signe centered checkbox created that is editable with a single click
-
@hachbani
I don't know how my solution relates to yours, but: I did this --- centered checkbox, editable. I did not do anypaint
overriding. I did not store the value inEditRole
(I left that empty), only inCheckState
role. I did use theAlignment
role indata()
to make the box be centered. And I did use @Christian-Ehrlicher's recent posted fix to correctly allow centered checkbox --- I'm hoping that by typing his name here he will point you to the code for this, as I can't recall which post it was in.Don't know if this helps... :)
-
@JonB Thanks for your reply ! hope Christian can save my life with a code snippet.
If I'm getting this right, there's no escape from getting the second checkbox to be created, instead a fix is to center it as well (so It overlays with the first one). Am I getting this right ?
-
@hachbani
Don't know, and finding it hard looking at my code to remember anything any more! :(I don't know all, but here is the nub of what I got from Christian. It's C++, but you seem competent, so should be doable from Python/PySide2:
// ********** CLASS CheckAlignmentProxyStyle ********** // /*virtual*/ QRect UIUtils::CheckAlignmentProxyStyle::subElementRect(QStyle::SubElement element, const QStyleOption *option, const QWidget *widget) const /*override*/ { // `QProxyStyle` to center checkboxes (from `Qt::ItemIsuserCheckable`) on item views (in `QTableView` etc.) // Code taken from https://forum.qt.io/topic/94049/how-to-center-a-column-with-a-checkbox-in-qtableview/8 // To apply this style outside world (e.g. `MainWindow`) must go `qApp->setStyle(new UIUtils::CheckAlignmentProxyStyle);` // and individual models' `flags()` need to go: // case QtExtra::CheckAlignmentRole: // return Qt::AlignHCenter; // call base class const QRect baseRes = QProxyStyle::subElementRect(element, option, widget); if (element == SE_ItemViewItemCheckIndicator) { // `SE_ItemViewItemCheckIndicator` => checkbox const QStyleOptionViewItem* const itemOpt = qstyleoption_cast<const QStyleOptionViewItem*>(option) ; Q_ASSERT(itemOpt); // read item's `CheckAlignmentRole` role value const QVariant alignData = itemOpt->index.data(QtExtra::CheckAlignmentRole); if (alignData.isNull()) return baseRes; const int alignFlag = alignData.toInt(); const QRect itemRect = option->rect; Q_ASSERT(itemRect.width()>baseRes.width() && itemRect.height()>baseRes.height()); int x = 0; if (alignFlag & Qt::AlignLeft) x = baseRes.x(); else if (alignFlag & Qt::AlignRight) x = itemRect.x() + itemRect.width() - (baseRes.x() - itemRect.x()) - baseRes.width(); else if (alignFlag & Qt::AlignHCenter) x = itemRect.x() + (itemRect.width() / 2) - (baseRes.width() / 2); return QRect(QPoint(x, baseRes.y()), baseRes.size()); } return baseRes; }
Go check out https://forum.qt.io/topic/94049/how-to-center-a-column-with-a-checkbox-in-qtableview/8
I see that is a @VRonin solution. Yet I remember interacting with @Christian-Ehrlicher. I think he did a fix so a future/current Qt version had it built-in so you wouldn't have to write this code. Hopefully he will clarify, this is what happens to one's memory as one gets older... :(
I see I added
namespace QtExtra { // extra `Qt::ItemDataRole` value for aligning checkboxes in tables // see `UIUtils::CheckAlignmentProxyStyle` enum { CheckAlignmentRole = Qt::UserRole + Qt::CheckStateRole + Qt::TextAlignmentRole }; }
I cannot recall, does this give you enough to go on?
P.S. So if I recall/understand this correctly, I'm not using
createEditor()
etc. at all. Rather, I'm just usingflags()
to includeQt.ItemIsUserCheckable
. User can check box without going into edit mode. So there is only one checkbox, no overlaying, I never create anyQCheckbox
, the value is stored inQt.CheckStateRole
. I think it is much simpler than your way. -
@JonB Thanks for your help ! It seems that the snippet you gave me does handle the alignement of the checkboxs, right ?
If this is what it is, I don't think I'm there yet, as I'm still trying to create Editable (with single click event) Checkboxs.I think I'm going to give up the Delegate method, and see If I can get the CheckStateRole method to work.
-
@hachbani
Yes, like I wrote at the end, this way you never create anyQCheckbox
yourself, and you don't go into editable mode withcreateEditor()
etc.The code is only to do with getting the checkbox centered. Just start out with using
CheckStateRole
to store the state andItemIsUserCheckable
in the flags. This allows the user to check the box without anything further.