Search code examples
pythonqtpyside6sparklines

How to refine fiddly overpainting of delegate rows in a QTreeView?


At the center of my python/Qt application is a tabular view using QTreeView. In one column, I have implemented sparklines by subclassing QStyledItemDelegate, and setting the data items in the appropriate column to be numpy arrays, and plotting these in the delegate's paint method.

My line plots go slightly outside the bounding boxes, but I like this effect (and I would like to keep it):

sparkline rows, showing overlapping lines

However, when I brush downwards, some repainting and somehow, some background repainting occurs, resulting in a temporary image where the neutral background breaks up the overpainted lines:

sparkline rows, showing broken lines

When I leave the window, or touch the header, some repainting happens again, back to the first image.

Questions

How and where is this background repainting happening, and what can I do to affect it? I am surprised because my user code overrides the delegates paint method and only draws lines.

My paint method

def paint(self, painter, option, index):
    
    data = index.data(UserRole+1)
    rectF = option.rect.toRectF()
    ndims = self.ndims
    yscale = self.yscale

    midHeight = rectF.top() + rectF.height()/2
    ctrLine = QtCore.QLineF( rectF.left(), midHeight,
        rectF.right(), midHeight )
    painter.setPen( self.midPen )
    painter.drawLine( ctrLine )

    painter.setPen( self.linePen )

    segWidth = rectF.width() / (ndims-1)
    for s in range(ndims-1):
        seg = data[s:s+2]
        segProj = -seg / yscale * rectF.height()
        segLine = QtCore.QLineF( rectF.left() + s*segWidth,
            midHeight + segProj[0],
            rectF.left() + (s+1)*segWidth,
            midHeight + segProj[1]  )
        painter.drawLine( segLine )

I tried setting item.setBackground( brush ) with either an opaque or a transparent QColor. Neither of these did anything because my paint code is never calling any background painting.


Solution

  • Update: based on the critique of @musicamante, I developed this approach based on QTreeWidget.itemEntered.

    First, I changed my code to be able to use QTreeWidget. I connected the itemEntered signal to an application-level function that calls emitDataChanged (from QTreeWidgetItem) on all items.

    This requests repaint on mouse hover, seeming to cover all the cases except for when the mouse exits the bottom item. This depicts this state:

    sparklines, with bottom row background obstructing above lines

    We want to detect the event where the mouse leaves the bottom item. In this approach, we develop a signal that detects leaving any item.

    In a subclass of QTreeWidget, we have a function that maintains a variable for the last entered item, and detects changes in this to emit a new itemExited Signal.

    def updateItemEntered(self, item):
        if item != self.lastItemEntered:
            self.itemExited.emit( self.lastItemEntered )
            self.lastItemEntered = item
    

    This function is called in response to the tree's itemEntered signal, as well as from within the override of mouseMoveEvent.

    def mouseMoveEvent(self,event):
        super().mouseMoveEvent(event)
        item = self.itemAt( event.position().toPoint() )
        self.updateItemEntered(item)
    

    As above, when itemExited is detected, update all items via emitDataChanged.

    In this approach, repaint requests are triggered only by the mouse crossing boundaries, avoiding any infinite recursion.