79140555

Date: 2024-10-30 10:03:57
Score: 0.5
Natty:
Report link

OK, thanks to the comment by @musicamante, I finally got to a solution.

The trick is basically to avoid creating and returning pixmaps based on QTableView cell/item size in TableModel.data(...) for Qt.DecorationRole (in this example, conditional on data for the cell/item being empty string) - and instead:

With this, handling for Qt.DecorationRole in TableModel.data(...) is not needed anymore...

And, the behavior with these changes (code at end of post) is exactly the same as described in OP for "no DecorationRole": at program start, you get this GUI state: (I've added a BackgroundRole and one more cell with text, just to make sure painting is OK)

corrected code - table view start

... and then when you click on "resizeToContents" for the first time after start, the GUI state changes to this:

corrected code - table view after resizeToContents

... and after this, you can click to your heart's content on "resizeToContents", and the cell size will not change!

The corrected code:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import (Qt, QSize, QPointF)
from PyQt5.QtGui import (QColor, QPalette, QPixmap, QBrush, QPen, QPainter)
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QPushButton, QStyleOptionViewItem, QStyledItemDelegate)
# starting point from https://www.pythonguis.com/tutorials/qtableview-modelviews-numpy-pandas/

class CustomItemDelegate(QStyledItemDelegate):
    def initStyleOption(self, opt, index):    # https://stackoverflow.com/q/79129869
        super().initStyleOption(opt, index)
        print(f"initStyleOption {opt.index=} {opt.text=} {opt.rect=} {opt.backgroundBrush=} {int(opt.features)=:#010b} {opt.decorationPosition=} {opt.decorationSize=}")
    #
    def create_pixmap(self, pic_qsize):       # https://stackoverflow.com/q/62799632
        pixmap = QPixmap(pic_qsize)
        pixmap.fill(QColor(0,0,0,0))
        painter = QPainter()
        painter.begin(pixmap)
        #painter.setBrush(QtGui.QBrush(QtGui.QColor("blue")))
        painter.setPen(QPen(QColor("#446600"), 4, Qt.SolidLine))
        painter.drawLine(pixmap.rect().bottomLeft(), pixmap.rect().center()+QPointF(0,4))
        painter.drawLine(pixmap.rect().bottomRight(), pixmap.rect().center()+QPointF(0,4))
        painter.drawLine(pixmap.rect().topLeft(), pixmap.rect().center()+QPointF(0,-4))
        painter.drawLine(pixmap.rect().topRight(), pixmap.rect().center()+QPointF(0,-4))
        painter.end()
        return pixmap
    #
    def paint(self, painter, opt, index):     # https://stackoverflow.com/q/70487748
        # for paint, see also https://stackoverflow.com/q/32032379
        # NOTE: opt.text is always "" here - options.text is actual cell/item text!
        options = QtWidgets.QStyleOptionViewItem(opt)
        self.initStyleOption(options, index)
        print(f"paint {opt.text=} {options.text=} {opt.rect=} {options.rect=}")
        # super.paint has to run first, if we want the pixmap on top of the BackgroundRole
        super(CustomItemDelegate, self).paint(painter, options, index)
        if options.text == "":
            pixmap = self.create_pixmap(options.rect.size())
            painter.drawPixmap(options.rect, pixmap)
    #
    ## NOTE: https://stackoverflow.com/q/9782553
    ## > sizeHint is useful only when resizeRowsToContents, ...
    ## > ..., resizeColumnsToContents, ... are called
    def sizeHint(self, opt, index):           # https://stackoverflow.com/q/70487748
        # for sizeHint, see also https://stackoverflow.com/q/71358160
        options = QtWidgets.QStyleOptionViewItem(opt)
        self.initStyleOption(options, index) # options.text ...
        print(f"sizeHint {opt.rect=} {options.rect=}")
        return super(CustomItemDelegate, self).sizeHint(options, index)

class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, data, parview):
        super(TableModel, self).__init__()
        self._data = data
        self._parview = parview # parent table view
    #
    def data(self, index, role):
        if role == Qt.DisplayRole:
            return self._data[index.row()][index.column()]
        if role == Qt.BackgroundRole:     # https://stackoverflow.com/q/57321588
            return QColor("#AAFF" + 2*str(index.column()*8))
        ## NO more need for DecorationRole for empty cell;
        ## CustomItemDelegate takes over that now!
        #if role == Qt.DecorationRole:    # https://stackoverflow.com/q/74203503
        #    value = self._data[index.row()][index.column()]
        #    if not(value):
        #        row_height = self._parview.rowHeight(index.row())        #-5
        #        column_width = self._parview.columnWidth(index.column()) #-13
        #        #item = self._parview.itemDelegate(index) # QStyledItemDelegate
        #        print(f"{row_height=} {column_width=}")
        #        pic_qsize = QSize(column_width, row_height)
        #        return self.create_pixmap(pic_qsize)
    #
    def rowCount(self, index):
        return len(self._data)
    #
    def columnCount(self, index):
        return len(self._data[0])
Reasons:
  • Blacklisted phrase (0.5): thanks
  • Blacklisted phrase (1): stackoverflow
  • Long answer (-1):
  • Has code block (-0.5):
  • User mentioned (1): @musicamante
  • Self-answer (0.5):
  • High reputation (-1):
Posted by: sdbbs