Search code examples
pythonpython-3.xpyqt6

How to update cell background color of an inactive QTableView/ QStyledItemDelegate?


My GUI is built by QTableView's.

Each QTableView has a QStyledItemDelegate.

In the QStyledItemDelegate the background color will be changed with:

    def initStyleOption(self, option, index):
        super(ValidatedIntItemDelegate, self).initStyleOption(option, index)
        option.backgroundBrush = self.calculate_color_for_column(index)

Detail: self.calculate_color_for_column(index) just does the validation of the cell value, dependent of the validity, a different color is returned.

All background coloring is working perfect as long I just edit within the same table. If I select a cell in another table, the last selected cell in the old table remains with a grey background not coming from my validation.

Scenario:

  • Edit in Leading edge->a1 value
  • Move with Tab or Mouse click to Leading edge->b1
  • Selected Trailing edge->a1 cell
  • Leading edge->b1 cell background is not updated

enter image description here

  • Not doing any edits in Trailing edge->a1 select again Leading edge->x1
  • ALL Leading edge cells are shown with correct background again!!
  • BUT Trailing edge->a1 has now the wrong background color

enter image description here

The cells not updating correctly the background color are the ones

  • selected
  • but in an inactive table/ delegate

So, how to catch this state and make sure the backround reflects the color returned from self.calculate_color_for_column(index)?


Solution

  • What you are seeing is not the background not being updated, but the highlight color that is used to show the selection.

    In order to ensure that the actual selected item can always be identified, the selection always has the same color, and completely disregards the background of the item. This is probably done for reasons related to UX aspects: for instance, if you use a color with alpha for the selection, you may end up confusing items that are not selected with other that are, but have a color that mixed up with the selection becomes similar to the others.

    Since you're using quite pale colors, a possibility could be to use a darker version of the color.

    To avoid inconsistency between styles, though, that color won't be used for the item background (the option.backgroundBrush) but for the option palette, using the Normal and Inactive roles with different values of "darkness".

    class Delegate(QStyledItemDelegate):
        def initStyleOption(self, opt, index):
            super().initStyleOption(opt, index)
            if opt.backgroundBrush.style() and opt.state & QStyle.State_Selected:
                color = opt.backgroundBrush.color()
                opt.palette.setColor(QPalette.Active, QPalette.Highlight, 
                    color.darker(150))
                opt.palette.setColor(QPalette.Inactive, QPalette.Highlight, 
                    color.darker(125))
    

    Another possibility would be what written above: use a blend of the background and the palette brush.

    Since, as explained, the delegate always ignores the color whenever the item is selected, we need to work around this:

    1. paint the basic item primitive PE_PanelItemViewItem without the State_Selected flag, so that it paints the background as expected;
    2. alter the opt palette in initStyleOption() and change the alpha of the Highlight roles;
    3. call the base implementation, which will paint over the previously drawn background;
    class Delegate(QStyledItemDelegate):
        def initStyleOption(self, opt, index):
            super().initStyleOption(opt, index)
            if opt.backgroundBrush.style() and opt.state & QStyle.State_Selected:
                normal = opt.palette.color(QPalette.Active, QPalette.Highlight)
                normal.setAlphaF(.5)
                opt.palette.setColor(QPalette.Active, QPalette.Highlight, normal)
                inactive = opt.palette.color(QPalette.Inactive, QPalette.Highlight)
                inactive.setAlphaF(.5)
                opt.palette.setColor(QPalette.Inactive, QPalette.Highlight, inactive)
    
        def paint(self, qp, opt, index):
            # we should *never* change the option provided in the arguments when
            # doing painting: that option is always reused for other items, and 
            # its properties are not always cleared
            vopt = QStyleOptionViewItem(opt)
            self.initStyleOption(vopt, index)
            widget = opt.widget
            style = widget.style()
            if vopt.backgroundBrush.style() and vopt.state & style.State_Selected:
                vopt.state &= ~style.State_Selected
                style.drawPrimitive(style.PE_PanelItemViewItem, vopt, qp, widget)
    
            # now we *do* use the original option
            super().paint(qp, opt, index)