如何解决QTreeView中委托行的精细重绘异常问题?
问题场景还原
我在自己的Python/Qt项目里用QTreeView做了个表格视图,其中一列通过子类化QStyledItemDelegate实现了迷你折线图(sparklines)——把对应列的数据存成numpy数组,然后在委托的paint方法里绘制折线。
我特意让折线稍微超出单元格边界,这个视觉效果挺不错的,想保留:
(效果说明:多行迷你折线图,折线跨单元格连贯重叠,视觉流畅)
但碰到个头疼的问题:当我向下拖动选择框(brush)时,原本连贯的折线会被中性背景打断,变成断开的样子:
(效果说明:多行迷你折线图,部分折线被背景色块截断,不再连贯)
不过只要切换窗口或者点击表头,视图又会重新正确渲染,回到最初的连贯状态。我还试过用item.setBackground(brush)设置不透明或透明的QColor,但完全没用——毕竟我的paint代码根本没调用任何背景绘制的逻辑。
我的paint方法代码如下:
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 )
问题根源分析
这个问题的核心是:折线超出了自身单元格的区域,进入了相邻单元格的范围。当你拖动选择框触发局部重绘时,QTreeView会只重绘被选中/更新的单元格区域——包括这些区域的默认背景,而这些背景会覆盖掉之前由其他单元格委托绘制过来的折线部分。只有当整个视图触发全量重绘(比如切换窗口、点击表头)时,所有单元格的折线才会重新绘制,恢复连贯。
解决方案推荐
针对这个问题,有几个可行的解决思路,你可以根据自己的需求选择:
1. 禁用QTreeView的默认单元格背景绘制
这是最直接的方法,通过样式表让视图不再自动绘制单元格背景,这样局部重绘时就不会出现覆盖折线的背景色块:
# 在初始化QTreeView的地方添加 tree_view.setStyleSheet("QTreeView::item { background: transparent; }")
如果需要保留选中状态的视觉反馈,可以在样式表里额外定义选中时的样式,比如:
tree_view.setStyleSheet(""" QTreeView::item { background: transparent; } QTreeView::item:selected { background: rgba(100, 180, 255, 100); } """)
2. 让委托主动控制背景绘制
在你的paint方法开头,用透明色填充当前单元格的区域,确保委托绘制的内容是在透明背景之上,同时覆盖视图的默认背景绘制:
def paint(self, painter, option, index): # 先填充透明背景,覆盖视图默认的背景绘制 painter.fillRect(option.rect, QtCore.Qt.transparent) # 下面是你原来的绘制代码 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 )
3. 触发相邻单元格的联动重绘
如果不想修改背景样式,可以在委托的paint方法中,当绘制超出边界的折线时,触发相邻单元格的更新,确保局部重绘时相关区域都能重新绘制:
def paint(self, painter, option, index): # 原来的绘制代码... # 绘制完成后,触发下方单元格的更新(如果折线超出当前单元格) next_index = index.sibling(index.row()+1, index.column()) if next_index.isValid(): self.parent().update(next_index)
不过这个方法可能会带来一定的性能开销,尤其是数据量较大时,需要谨慎使用。
备注:内容来源于stack exchange,提问作者Graham

