Issue
I've got this simple code in the paintEvent of my QTextEdit which draws a grey box under the currently selected 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)
(Note, the "clean up artifacts" line erases anything drawn in the region of the previously selected QTextBlock, since a thin grey line would remain under the last block if text was drawn in it. Might be related.)
However, when the cursor is moved via clicking on another line, this happens:
The next rectangle is only partially drawn, around the character which the cursor was moved to, and the previous one is not erased. eraseRect() doesn't seem to be able to remove this artifact. When typing is continued or a newline is made, everything goes back to normal (this issue never occurs when changing lines via making a newline). I've confirmed that paintEvent() is called when the cursor moves, and the width of the rectangles to be drawn never changes. What's happening here?
Solution
For optimization reasons, Qt only try to repaint only the portions of the widget that actually need updates.
In the case of a QTextEdit, this means that only the portions in which the "caret" was before moving it (by editing, using arrow keys or mouse) and it is now will be updates, while ignoring everything else.
This clearly is an issue in your case, because it will only update small portions of the widget, with the result that the previously highlighted block will not be redrawn showing your custom background.
The solution is to track the current cursor position and correctly update both the previous block and the new one as soon as the block changes. This is achieved by calling update()
using a QRegion, created by merging the current block bounding rect and the previous (if any); this will schedule an update that only redraws the contents within that region (which is what QTextEdit normally does, but we're extending it to the whole block area and the previous).
Note that I've completely changed your implementation of the paintEvent, as it was mostly unnecessary, for the following reasons:
- the margins of the document should not be used for the block;
- the background painting doesn't consider the scroll area background (more about this later);
- there is no need to "erase" the previous block rectangle: our
update()
call includes that area, and since it's not the current block, the background will be just (re)drawn there instead; - the current block shouldn't be updated in the paint event;
The rendering of a scroll area always involves painting of its background based on the backgroundRole
, which is automatically set to the Base
palette role for QTextEdit. The result is that your background color will not be what you believe, but a composition of the base (usually, a nearly white color) and your background.
In order to ensure that the background is exactly the color you want, you have to update the palette on the widget using that color for that Base
role, which should also be an opaque color (otherwise it will be composed using the Window
color role).
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)
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.