Reputation: 742
I am using a subclassed QItemDelegate to display and edit cells in a QTableView. The editing part works perfectly.
The issue I have is that for every cell that is editable, the background stays white when the row is selected (I use a row selection behavior).
I have tried playing with the paint() method but whatever I do the background for those cells does not change when the row is selected.
My internet research leads me to think that because the different widgets used to edit the cells take up the entire cell rect (since that is how it is defined in the updateEditorGeometry() method), the background of the cell if hidden.
How can i fix this so that the background of the widget used to edit the cell gets the same selected background as the rest of the row.
Here is an image displaying the issue:
Each of the cells that still has a white background is editable and is defined in the QItemDelegate class.
Here is the code for the delegate class:
class SqlAlchemyItemDelegate(QtWidgets.QItemDelegate):
def __init__(self):
super().__init__()
def paint(self, painter: QtGui.QPainter, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> None:
# Checkboxes
check_state = index.data(QtCore.Qt.CheckStateRole)
if isinstance(check_state, int):
self.drawCheck(painter, option, option.rect, check_state)
self.drawFocus(painter, option, option.rect)
return
super().paint(painter, option, index)
def editorEvent(self, event, model, option, index):
if event.type() == QtCore.QEvent.MouseButtonRelease and isinstance(index.data(QtCore.Qt.DisplayRole), bool):
value = bool(model.data(index, QtCore.Qt.CheckStateRole))
model.setData(index, not value)
event.accept()
return super().editorEvent(event, model, option, index)
def createEditor(self, parent: QtWidgets.QWidget, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> QtWidgets.QWidget:
data = index.model().data(index, QtCore.Qt.EditRole)
if isinstance(data, datetime.datetime):
return QtWidgets.QDateTimeEdit(parent)
if isinstance(data, str):
return QtWidgets.QLineEdit(parent)
def setEditorData(self, editor: QtWidgets.QWidget, index: QtCore.QModelIndex) -> None:
data = index.model().data(index, QtCore.Qt.EditRole)
if isinstance(editor, QtWidgets.QDateTimeEdit):
editor.setDateTime(data)
if isinstance(editor, QtWidgets.QLineEdit):
editor.setText(data)
def setModelData(self, editor: QtWidgets.QWidget, model: QtCore.QAbstractItemModel, index: QtCore.QModelIndex) -> None:
if isinstance(editor, QtWidgets.QDateTimeEdit):
model.setData(index, editor.dateTime().toPyDateTime(), QtCore.Qt.EditRole)
if isinstance(editor, QtWidgets.QLineEdit):
model.setData(index, editor.text(), QtCore.Qt.EditRole)
def updateEditorGeometry(self, editor: QtWidgets.QWidget, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> None:
editor.setGeometry(option.rect)
EDIT
Here is a minimal working example that reproduces the issue:
from PyQt5 import QtWidgets, QtCore, QtGui
import datetime
class Gui(QtWidgets.QDialog):
def __init__(self):
super().__init__()
self.setMinimumWidth(750)
self.table_view = QtWidgets.QTableView()
self.table_view.setSelectionBehavior(self.table_view.SelectRows)
self.table_model = SqlAlchemyTableModel()
self.table_model.from_complex_query(
table_data=[
[1, 'Alex', 'Knight', '28', '0123456789', True, datetime.datetime(year=2021, month=5, day=12, hour=2, minute=30, second=30)],
[2, 'Alex', 'Knight', '28', '0123456789', True, datetime.datetime(year=2021, month=5, day=12, hour=2, minute=30, second=30)],
[3, 'Alex', 'Knight', '28', '0123456789', True, datetime.datetime(year=2021, month=5, day=12, hour=2, minute=30, second=30)]
],
headers=['id', 'first', 'last', 'age', 'phone', 'checked', 'date'],
editable_columns=[4, 5, 6]
)
self.table_view.setModel(self.table_model)
self.delegate = SqlAlchemyItemDelegate()
self.table_view.setItemDelegate(self.delegate)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.addWidget(self.table_view)
self.setLayout(self.layout)
self.show()
class SqlAlchemyItemDelegate(QtWidgets.QItemDelegate):
def __init__(self):
super().__init__()
def paint(self, painter: QtGui.QPainter, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> None:
# Checkboxes
check_state = index.data(QtCore.Qt.CheckStateRole)
if isinstance(check_state, int):
self.drawCheck(painter, option, option.rect, check_state)
self.drawFocus(painter, option, option.rect)
return
super().paint(painter, option, index)
def editorEvent(self, event, model, option, index):
if event.type() == QtCore.QEvent.MouseButtonRelease and isinstance(index.data(QtCore.Qt.DisplayRole), bool):
value = bool(model.data(index, QtCore.Qt.CheckStateRole))
model.setData(index, not value)
event.accept()
return super().editorEvent(event, model, option, index)
def createEditor(self, parent: QtWidgets.QWidget, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> QtWidgets.QWidget:
data = index.model().data(index, QtCore.Qt.EditRole)
if isinstance(data, datetime.datetime):
return QtWidgets.QDateTimeEdit(parent)
if isinstance(data, str):
return QtWidgets.QLineEdit(parent)
def setEditorData(self, editor: QtWidgets.QWidget, index: QtCore.QModelIndex) -> None:
data = index.model().data(index, QtCore.Qt.EditRole)
if isinstance(editor, QtWidgets.QDateTimeEdit):
editor.setDateTime(data)
if isinstance(editor, QtWidgets.QLineEdit):
editor.setText(data)
def setModelData(self, editor: QtWidgets.QWidget, model: QtCore.QAbstractItemModel, index: QtCore.QModelIndex) -> None:
if isinstance(editor, QtWidgets.QDateTimeEdit):
model.setData(index, editor.dateTime().toPyDateTime(), QtCore.Qt.EditRole)
if isinstance(editor, QtWidgets.QLineEdit):
model.setData(index, editor.text(), QtCore.Qt.EditRole)
def updateEditorGeometry(self, editor: QtWidgets.QWidget, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> None:
editor.setGeometry(option.rect)
class SqlAlchemyTableModel(QtCore.QAbstractTableModel):
def __init__(self, **kwargs):
super().__init__()
def rowCount(self, *args):
return len(self.table_data)
def columnCount(self, *args):
return len(self.headers)
def data(self, index, role):
row = index.row()
col = index.column()
# EditRole is handled by SqlAlchemyItemDelegate
if role == QtCore.Qt.EditRole:
return self.table_data[row][col]
# For Booleans, display checkbox
if isinstance(self.table_data[row][col], bool):
if role == QtCore.Qt.CheckStateRole:
if self.table_data[row][col]:
return QtCore.QVariant(QtCore.Qt.Checked)
else:
return QtCore.QVariant(QtCore.Qt.Unchecked)
return self.table_data[row][col]
# For DateTime, display QDateTime
if isinstance(self.table_data[row][col], datetime.datetime) and role == QtCore.Qt.DisplayRole:
return QtCore.QDateTime(self.table_data[row][col])
# For other data, display itself
if role == QtCore.Qt.DisplayRole:
return self.table_data[row][col]
def headerData(self, section, orientation, role):
if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal:
return self.headers[section]
def flags(self, index):
if self.editable_columns and index.column() in self.editable_columns and isinstance(self.table_data[index.row()][index.column()], bool):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable
if self.editable_columns and index.column() in self.editable_columns and isinstance(self.table_data[index.row()][index.column()], (str, datetime.datetime)):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable
return super().flags(index)
def setData(self, index, value, role):
row = index.row()
col = index.column()
if role == QtCore.Qt.EditRole:
self.table_data[row][col] = value
self.dataChanged.emit(index, index)
return True
return super().setData(index, value, role)
def from_complex_query(self, table_data, headers, editable_columns=None):
self.table_data = [list(row) for row in table_data]
self.headers = headers
self.editable_columns = editable_columns
if __name__ == '__main__':
app = QtWidgets.QApplication([])
win = Gui()
app.exec()
Upvotes: 0
Views: 301
Reputation: 48260
The problem was not (completely) on the delegate, but in an incomplete implementation of the model: flags()
should also return QtCore.Qt.ItemIsSelectable
in order to allow (and display) selections.
def flags(self, index):
if self.editable_columns and index.column() in self.editable_columns and isinstance(self.table_data[index.row()][index.column()], bool):
return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable |
QtCore.Qt.ItemIsSelectable)
if self.editable_columns and index.column() in self.editable_columns and isinstance(self.table_data[index.row()][index.column()], (str, datetime.datetime)):
return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable |
QtCore.Qt.ItemIsSelectable)
return super().flags(index)
Then, using QItemDelegate, drawBackground()
is required too:
def paint(self, painter: QtGui.QPainter, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> None:
check_state = index.data(QtCore.Qt.CheckStateRole)
if isinstance(check_state, int):
self.drawBackground(painter, option, index)
# ...
Note that your code has other issues I'm not able to address right now. For instance, setData()
should provide the role as optional argument (in fact, your code throws an error when clicking on the checkbox), and changing a value that is used for a checkstate with setData should use the appropriate role.
Upvotes: 1