3

How should I change just the background color of my tree item using a delegate?

I haven't been able to figure out how to do this without painting on top of the text. In other words, when I use the code below, the color is drawn on top of the text. The text is under the background...

def paint(self, painter, option, index):
    MyDelegate.paint(self, painter, option, index)

    painter.save()

    # set background color
    painter.setPen(QPen(Qt.NoPen))
    if self.item.is_set and option.state & QStyle.State_Selected:
        painter.setBrush(QBrush(QtGui.QColor(100, 200, 0, 200)))
    else:
        painter.setBrush(QBrush(Qt.NoBrush))

    painter.drawRect(option.rect)

    painter.restore()

I want to inherit as much as I can.

...

Regarding the comments, I tried to apply the answer " Set color to a QTableView row " ... but I can't seem to get it to work in Python. I'm not sure how the optionV4.backgroundBrush() as any effect. Is the option used by reference so that this change is supposed to be used without a return value?

(These are used without overriding paint)

def initStyleOption(self, option, index):
    print("initStyleOption...")
    MyBaseDelegate.initStyleOption(self, option, index)

    optionV4 = QtGui.QStyleOptionViewItemV4(option)
    optionV4.backgroundBrush = QBrush(QtGui.QColor(100, 200, 100, 200))

I also tried this:

def initStyleOption(self, option, index):
    MyBaseDelegate.initStyleOption(self, option, index)
    option.backgroundBrush = QBrush(QtGui.QColor(100, 200, 100, 200))

...and this:

def initStyleOption(self, option, index):
    option.backgroundBrush = QBrush(QtGui.QColor(100, 200, 100, 200))
    MyBaseDelegate.initStyleOption(self, option, index)

And according to the docs, this should not return anything, so I assume option IS passed by reference, which makes sense.

I found another example, which works (though I haven't tried to understand it)

def paint(self, painter, option, index):
    option = QtGui.QStyleOptionViewItemV4(option)  # Needed for "widget"
    self.initStyleOption(option, index)  # <--- I did not override this

    option.backgroundBrush = QBrush(QtGui.QColor(100, 200, 100, 200))
    option.widget.style().drawControl(QStyle.CE_ItemViewItem, option, painter)

    ColumnBaseDelegate.paint(self, painter, option, index)

...but the background color is painted over by the selection color. Since I am trying to change the background color when the item is selected, this is a problem. Is there a different brush or method for working with selection colors? I need certain types of items to be a different color.

I tried the answer which demonstrates inverting the selection state before drawing my own background, but it painted an empty background for me for some reason. Putting it in paint worked though...

def paint(self, painter, option, index):

    if option.state & QStyle.State_Selected:
        option.state &= ~QStyle.State_Selected  # Invert state

        # This "cast", which is needed to get option.widget, seems to be
        #   needed for backgroundBrush as well.
        option = QtGui.QStyleOptionViewItemV4(option)  # Cast for widget
        option.backgroundBrush = QBrush(QColor(100, 200, 100, 200))

        option.widget.style().drawControl(QStyle.CE_ItemViewItem, option, painter)

    super(MyDelegate, self).paint(painter, option, index)

I don't understand why, so I won't add it to an answer at this point.

Community
  • 1
  • 1
Rafe
  • 1,937
  • 22
  • 31
  • Perhaps http://stackoverflow.com/questions/10630360/customized-color-on-progressbar-delegate/10630476#10630476 question will help you. See my answer in it. Although that particular question is about styling progress-bar, same can be done for tree-view also. – Ammar Jun 07 '12 at 06:02
  • possible duplicate of [Set color to a QTableView row](http://stackoverflow.com/questions/10219739/set-color-to-a-qtableview-row) – alexisdm Jun 07 '12 at 15:42
  • @alexisdm: I'm having trouble getting that to do something in Python. See my post – Rafe Jun 07 '12 at 23:48
  • @Ammar: I don't see any delegate being used there. Are you saying applying styles is the way to go? Can a style be applied directly to an index? Can you post a simple generic example for a regular item background? – Rafe Jun 07 '12 at 23:48
  • The assignation was a type cast in C++, but does a copy in python. You don't need that cast here, just `option.backgroundBrush = QBrush(...)`. And yes, the `option` argument is the return value. – alexisdm Jun 08 '12 at 08:13
  • @alexisdm: I added what I tried to my question above. If you have a working example, please post it. The function is being called (my print line works), but it doesn't seem to have any effect. Do I need something in another function? Do I need paint() still (I commented it out)? Thanks again for the help. I hope I am getting close. – Rafe Jun 08 '12 at 17:04

1 Answers1

4

To override background color for selected items, you can disable the selected flag in initStyleOption. For example:

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class SelectionColorDelegate(QStyledItemDelegate):
        def __init__(self, parent):
        super(SelectionColorDelegate, self).__init__(parent)

    def initStyleOption(self, option, index):
        # let the base class initStyleOption fill option with the default values
        super(SelectionColorDelegate,self).initStyleOption(option, index)
        # override what you need to change in option
        if option.state & QStyle.State_Selected:
            option.state &= ~ QStyle.State_Selected
            option.backgroundBrush = QBrush(QColor(100, 200, 100, 200))

if __name__ == "__main__":
    app = QApplication(sys.argv)    
    treeWidget = QTreeWidget()
    treeWidget.setColumnCount(2)
    for i in range(5):
        item = QTreeWidgetItem(["Item %d"%i, "data" ])
        treeWidget.addTopLevelItem(item)
        for j in range(3):
            subItem = QTreeWidgetItem(["SubItem %d, %d"%(i,j), "subdata"])
            item.addChild(subItem)
        treeWidget.expandItem(item)

    treeWidget.setItemDelegate(SelectionColorDelegate(treeWidget))    
    treeWidget.show()   

    app.exec_()
    sys.exit()

Apparently, the background is also painted by QTreeView.drawRow before the delegate paint function is called, so for indented items, a part of that background keeps the default color.

alexisdm
  • 29,448
  • 6
  • 64
  • 99
  • Please see the edit to my answer. This didn't work for me for some reason. It was still very helpful though! I'll have to look at the tree's `drawRow` and fake it there. Leaving the first part of the selection background drawn in the original color isn't pretty enough. – Rafe Jun 11 '12 at 17:37
  • There is still something more going on. I tried to override drawRow, but it painted the background for the entire row, event though it only ever receives index.column() 0. For some reason, the default behavior does not force a colored background on my other columns, which are handled by a delegate which simply doesn't draw the background at all (normally looks like only column 0 is selected, visually). I would love to see a complete python implementation of drawRow and a delegate's paint. This would solve so many questions. – Rafe Jun 11 '12 at 22:04