I am open to better alternatives, but what I got here is a checkbox that uses a color code of green and red instead of 'checked' and 'unchecked'.
I am using this with a custom QStyledItemDelegate, QTableView and QAbstractTableModel, with the intent of storing the state of the 'checkbox'.
The issue is the delegate setModelData is not called upon mouse press. Note that this issue is not present in any of my other custom widgets (e.g. custom QDateEdit and QLineEdit), for which I never had to do anything (in the widget code) to make the setModelData call happen.
This is why I am sure the problem is that I am missing something in this class code. My guess is a signal or the implementation of some method that defaults to a no-op in the code of QToolButton. Which one, though?
EDIT: a full example of was requested, the relevant code is ColorCheckBox, the rest is necessary to make it run. When you click on the box it should print the changed value, but aside for twice at the beginning, it does not.
from PyQt5 import QtCore, QtWidgets
import sys
def validate(value, expected_type):
if type(value) == QtCore.QVariant:
value = None if value.isNull() or not value.isValid() else value.value()
if expected_type is bool and not value:
return False
value = expected_type() if value is None else expected_type(str(value))
return value
class ColorCheckBox(QtWidgets.QToolButton):
def __init__(self, parent=None):
super().__init__(parent)
self.bool = False
self.setStyleSheet("background-color: red")
def data(self):
return self.bool
def set_data(self, value):
self.set_checked(validate(value, bool))
self.update()
def set_checked(self, value):
self.bool = value
if self.bool:
self.setStyleSheet("background-color: green")
else:
self.setStyleSheet("background-color: red")
def mousePressEvent(self, event):
super().mousePressEvent(event)
if event.button() == QtCore.Qt.LeftButton:
self.set_data(not self.bool)
class MyWindow(QtWidgets.QWidget):
def __init__(self, *args):
super().__init__(*args)
tableview = TableView()
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(tableview)
self.setLayout(layout)
class Delegate(QtWidgets.QStyledItemDelegate):
def __init__(self, model):
super().__init__()
self.model = model
def createEditor(self, parent, option, index):
widget = ColorCheckBox(parent)
widget.set_data(False)
return widget
def setModelData(self, widget, model, index):
self.model.setData(index, widget.data())
def setEditorData(self, widget, index):
widget.set_data(index.data())
class Model(QtCore.QAbstractTableModel):
def __init__(self, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent=parent)
def flags(self, index):
return super().flags(index) | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid() or role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
return QtCore.QVariant('False')
def setData(self, index, value, role=QtCore.Qt.EditRole):
print("data[{}][{}] = {}".format(index.row(), index.column(), value))
return True
def rowCount(self, parent=QtCore.QModelIndex()):
return 1
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
class TableView(QtWidgets.QTableView):
def __init__(self, parent=None):
super().__init__(parent)
self.model = Model(self)
delegate = Delegate(self.model)
self.setItemDelegate(delegate)
self.setEditTriggers(self.CurrentChanged)
self.setModel(self.model)
for row in range(self.model.rowCount()):
for column in range(self.model.columnCount()):
index = self.model.index(row, column)
self.openPersistentEditor(index)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())