Reputation: 3
I'm trying to create a custom TableModel
class for QTableView
. The cells containing 1
as data must have red outlining. Outlining is made by returning a pixmap (with red borders and text drawn on top) from TableModel
instead of returning a simple string.
The problem is in the unexpected padding of the pixmap, which I return as DecorationRole
. I checked if the pixmap is drawn correctly (and it actually is 21x21 px with well done outlining, without padding, just as planned) right before the return pixmap
line.
Here is the right drawn pixmap, which was saved just before the return
from TableModel:
Eventually, something shifts the returned pixmap by exactly 3px from the left border of the QTableView
cell. I didn't set any padding in QtDesigner for the QTableView
and didn't change it later in my code. I also tried to manually set up padding to zero using stylesheet, but it gave no different result.
Any ideas how to fix it? Thank you.
Here is the sample of my TableModel
:
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, topology=None):
super().__init__()
...
# Hardcode cell size and path to rectangle image
self.cell_width, self.cell_height = 21, 21
self.fpath_red_rect = './path/to/red_rect.png'
def rowCount(self, parent=QtCore.QModelIndex()):
return self.data.shape[0]
def columnCount(self, parent=QtCore.QModelIndex()):
return self.data.shape[1]
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
...
def size(self):
return QtCore.QSize((self.columnCount() + 1) * self.cell_width,
(self.rowCount() + 1) * self.cell_height)
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return QtCore.QVariant()
i = index.row()
j = index.column()
if role == QtCore.Qt.DisplayRole:
if self.data[i, j] == 0: # empty
return ''
elif self.data[i, j] == 1: # cell with red rectangle
# the text will be drawn on pixmap manually later
return None
else:
return '{0}'.format(self.data[i, j]) # display default data
if role == QtCore.Qt.DecorationRole:
# Create pixmap, draw the rectangle on it and then draw text on top
pixmap = QtGui.QPixmap(self.cell_width, self.cell_height)
image = QtGui.QImage(self.fpath_red_rect).scaled(self.cell_width, self.cell_height)
painter = QtGui.QPainter(pixmap)
painter.drawImage(pixmap.rect().topLeft(), image)
painter.drawText(pixmap.rect(), QtCore.Qt.AlignCenter, '{0}'.format(self.data[i, j]))
painter.end()
# If we save the pixmap to PNG image here (see the link above),
# we get the expected 21 x 21 px image, with nice
# and properly drawn rectangle and centered text.
# But something goes wrong after returning
return pixmap
if role == QtCore.Qt.BackgroundRole:
return QtGui.QBrush(self.getQtColor(self.data[i, j]))
if role == QtCore.Qt.TextAlignmentRole:
return QtCore.Qt.AlignCenter
return QtCore.QVariant()
Upvotes: 0
Views: 550
Reputation: 243897
DecorationRole
is used to draw the icon for that reason you observe the displacement, in your case you should not use that role, besides the painting task should not be done in the model since he only has to provide the data, if you want to modify the drawing a better option is use a delegate as I show below:
import sys
import numpy as np
from PyQt5 import QtCore, QtGui, QtWidgets
ValueRole = QtCore.Qt.UserRole + 1
max_val = 4
colors = [QtGui.QColor(*np.random.randint(255, size=3)) for i in range(max_val)]
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self.data = np.random.randint(max_val, size=(10, 10))
def rowCount(self, parent=QtCore.QModelIndex()):
return self.data.shape[0]
def columnCount(self, parent=QtCore.QModelIndex()):
return self.data.shape[1]
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return QtCore.QVariant()
i = index.row()
j = index.column()
val = self.data[i, j]
if role == QtCore.Qt.DisplayRole:
return str(val)
elif role == QtCore.Qt.TextAlignmentRole:
return QtCore.Qt.AlignCenter
elif role == QtCore.Qt.BackgroundRole:
return colors[val]
if role == ValueRole:
return val
return QtCore.QVariant()
class Delegate(QtWidgets.QStyledItemDelegate):
def paint(self, painter, option, index):
QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
if index.data(ValueRole) == 1:
painter.save()
pen = painter.pen()
pen.setColor(QtCore.Qt.red)
painter.setPen(pen)
r = QtCore.QRect(option.rect)
r.adjust(0, 0, -pen.width(), -pen.width())
painter.drawRect(r)
painter.restore()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QTableView()
w.setItemDelegate(Delegate(w))
model = TableModel()
w.setModel(model)
w.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
w.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
for i in range(model.rowCount()):
w.verticalHeader().resizeSection(i, 21)
for j in range(model.columnCount()):
w.horizontalHeader().resizeSection(j, 21)
w.show()
sys.exit(app.exec_())
Upvotes: 1