移动光标时 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)
我在 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)