Python 移动光标时,QTextEdit中的drawRect()无法正常工作

Python 移动光标时,QTextEdit中的drawRect()无法正常工作,python,pyqt,pyqt5,qtextedit,Python,Pyqt,Pyqt5,Qtextedit,我的QTextEdit的paintEvent中有一个简单的代码,它在当前选定的QTextBlock下绘制一个灰色框: def paintEvent(self, ev): painter = QPainter() painter.begin(self.viewport()) currentPos = self.textCursor().position() block = self.document().findBlock(currentPos) rect

我的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区域中绘制的任何内容,因为如果在最后一个块中绘制了文本,则会在最后一个块下保留一条细灰线。这可能是相关的。)

其效果是:

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


下一个矩形仅在光标移动到的字符周围部分绘制,而上一个矩形不会被擦除。橡皮擦Rect()似乎无法删除此工件。当继续键入或换行时,一切都会恢复正常(当通过换行更改行时,不会出现此问题)。我已经确认,当光标移动时会调用paintEvent(),并且要绘制的矩形的宽度不会改变。这里发生了什么?

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

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

在您的情况下,这显然是一个问题,因为它只会更新小部件的一小部分,从而不会重新绘制先前高亮显示的块,以显示您的自定义背景

解决方案是跟踪当前光标位置,并在块更改后立即正确更新上一个块和新块。这是通过使用a调用
update()
,通过合并当前块边界rect和上一个块边界rect(如果有)创建;这将安排一次更新,只重新绘制该区域内的内容(这是QTextEdit通常所做的,但我们将其扩展到整个块区域和上一个区域)

请注意,我已经完全更改了paintEvent的实现,因为它基本上是不必要的,原因如下:

  • 文件的边距不应用于块
  • 背景画不考虑滚动区域背景(稍后对此更详细);<李>
  • 无需“擦除”上一个块矩形:我们的
    update()
    调用包括该区域,由于它不是当前块,因此背景将被(重新)绘制在那里
  • 绘制事件中不应更新当前块
滚动区域的渲染始终涉及基于的背景绘制,该背景自动设置为QTextEdit的
Base
调色板角色。结果是,你的背景色不是你所相信的,而是由底色(通常是接近白色)和背景组成。
为了确保背景正是您想要的颜色,您必须使用该
Base
角色的颜色更新小部件上的调色板,该颜色也应该是不透明的颜色(否则将使用
窗口
颜色角色合成)

请提供一份
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())
        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)
        painter.fillRect(rect, QtGui.QColor(10, 10, 10,20))
        super().paintEvent(ev)