Search code examples
pythonqtpyqtpysideqstyleditemdelegate

How do I draw a styled focus rectangle in a QStyledItemDelegate?


The following question from Benno Dielmann on the PyQt mailing list has gone unanswered since 2008:

[..] I've got a QStyledItemDelegate subclass which implements paint() to draw the contents of some QTableView cells. How do I make it paint a focus rectangle if one of those cells has got focus? I tried this:

class MyDelegate(QStyledItemDelegate):  
    ...
    def paint(self, painter, option, index):
        ...
        painter.save()
        if option.state & QStyle.State_HasFocus:
           self.parent().style().drawPrimitive(QStyle.PE_FrameFocusRect, option, painter)
        ...
        painter.restore()

but this simply does nothing. No errors, no focus frame. I just want the QStyle system to somehow paint the usual focus frame if one of my custom painted cells have focus. The QStyle documentation tells me to create a QStyleOptionFocusRect and to use initFrom(). But initFrom() needs a QWidget which I don't have in this case.

I just don't get it.

What's the usual way to get focus frames in QTableView cells painted by custom delegates?[..]


Solution

  • I ran into the same problem. After much frustration, I found the answer buried in the deprecated QStyledItem class. Here's the PyQt/PySide solution based on that code:

    class MyDelegate(QtGui.QStyledItemDelegate):
        ...
        def drawFocus(self, painter, option, rect, widget=None):
            if (option.state & QtGui.QStyle.State_HasFocus) == 0 or not rect.isValid():
                return
            o = QtGui.QStyleOptionFocusRect()
            # no operator= in python, so we have to do this manually
            o.state = option.state
            o.direction = option.direction
            o.rect = option.rect
            o.fontMetrics = option.fontMetrics
            o.palette = option.palette
    
            o.state |= QtGui.QStyle.State_KeyboardFocusChange
            o.state |= QtGui.QStyle.State_Item
            cg = QtGui.QPalette.Normal if (option.state & QtGui.QStyle.State_Enabled) else QtGui.QPalette.Disabled
            o.backgroundColor = option.palette.color(cg, QtGui.QPalette.Highlight if (option.state & QtGui.QStyle.State_Selected) else QtGui.QPalette.Window)
            style = widget.style() if widget else QtGui.QApplication.style()
            style.drawPrimitive(QtGui.QStyle.PE_FrameFocusRect, o, painter, widget)
    
        def paint(self, painter, option, index):
            painter.save()
            # ... draw your delegate here, or call your widget's render method ...
            painter.restore()
    
            painter.save()
            # omit the last argument if you're not drawing a widget
            self.drawFocus(painter, option, option.rect, widget)
            painter.restore()