移动光标时 QTextEdit 中的 drawRect() 无法正常运行

drawRect() in QTextEdit not functioning properly when cursor is moved

我在 QTextEdit 的 paintEvent 中得到了这个简单的代码,它在当前选定的 QTextBlock 下绘制了一个灰色框:

def paintEvent(self, ev):
    painter = QPainter()
    painter.begin(self.viewport())
    currentPos = self.textCursor().position()
    block = self.document().findBlock(currentPos)
    rect = self.document().documentLayout().blockBoundingRect(block)
    margin = self.document().documentMargin()
    rect.setTopLeft(QPoint(int(rect.topLeft().x()-margin), int(rect.topLeft().y()-margin)))
    rect.setBottomRight(QPoint(int(rect.bottomRight().x()+margin), int(rect.bottomRight().y())))
    painter.fillRect(rect, QBrush(QColor(10, 10, 10,20)))
    if self._last_selected_block and (self._last_selected_block != block):
        lastrect = self.document().documentLayout().blockBoundingRect(self._last_selected_block) #clean up artifacts
        painter.eraseRect(lastrect)
    painter.fillRect(self.contentsRect(), QBrush(QColor(123, 111, 145, 80))) #background color
    painter.end()
    self._last_selected_block = block
    super().paintEvent(ev)

(请注意,“清除工件”线会擦除在先前选择的 QTextBlock 区域中绘制的任何内容,因为如果在其中绘制文本,最后一个块下方会保留一条灰色细线。可能是相关的。)

这样做的效果是:

但是,当通过单击另一行移动光标时,会发生这种情况:

下一个矩形只是部分绘制,围绕着光标移动到的字符,前一个矩形没有被擦除。 eraseRect() 似乎无法删除此工件。当继续打字或换行时,一切都会恢复正常(通过换行换行时不会出现此问题)。我已经确认 paintEvent() 在光标移动时被调用,并且要绘制的矩形的宽度永远不会改变。这里发生了什么?

出于优化原因,Qt 仅尝试重绘小部件中实际需要更新的部分。

在 QTextEdit 的情况下,这意味着只有“插入符号”在移动它之前是的部分(通过编辑,使用箭头键或鼠标)和它现在将是更新,同时忽略其他所有内容。

这显然是您的问题,因为它只会更新小部件的一小部分,结果是不会重新绘制先前突出显示的块以显示您的自定义背景。

解决方案是跟踪当前光标位置并正确更新前一个块新块变化。这是通过使用 QRegion 调用 update() 来实现的,该 QRegion 通过合并当前块边界 rect 和前一个(如果有的话)创建;这将安排一个仅重绘该区域内内容的更新(这是 QTextEdit 通常所做的,但我们将其扩展到整个块区域 前一个)。

请注意,我已经完全更改了您对 paintEvent 的实现,因为它几乎没有必要,原因如下:

  • 文档的边距不应用于块;
  • 背景绘画不考虑滚动区域背景(稍后详细介绍);
  • 无需“擦除”之前的块矩形:我们的 update() 调用包括该区域,并且由于它不是当前块,因此只会(重新)绘制背景;
  • 当前块不应在绘制事件中更新;

滚动区域的渲染总是涉及基于 backgroundRole 绘制其背景,它自动设置为 QTextEdit 的 Base 调色板角色。结果是您的背景颜色将不是您所相信的,而是底色(通常是接近白色)您的组合背景.
为了确保背景正是您想要的颜色,您必须使用该 Base 角色的颜色更新小部件上的调色板,该颜色也应该是不透明的颜色(否则它将使用Window颜色作用)。

class TextEdit(QtWidgets.QTextEdit):
    _last_selected_block = None
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        palette = self.palette()
        palette.setColor(palette.Base, QtGui.QColor(203, 200, 210))
        self.setPalette(palette)
        self.cursorPositionChanged.connect(self.trackCursorPosition)

    def trackCursorPosition(self):
        block = self.textCursor().block()
        currentRect = self.document().documentLayout().blockBoundingRect(block)
        updateRegion = QtGui.QRegion(currentRect.toRect())
        if self._last_selected_block and self._last_selected_block != block:
            oldRect = self.document().documentLayout().blockBoundingRect(
                self._last_selected_block)
            updateRegion |= QtGui.QRegion(oldRect.toRect())
        updateRegion.translate(0, -self.verticalScrollBar().value())
        self._last_selected_block = block
        self.viewport().update(updateRegion)

    def paintEvent(self, ev):
        painter = QtGui.QPainter(self.viewport())
        block = self.textCursor().block()
        rect = self.document().documentLayout().blockBoundingRect(block)
        rect.translate(0, -self.verticalScrollBar().value())
        painter.fillRect(rect, QtGui.QColor(10, 10, 10,20))
        super().paintEvent(ev)