You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

QAbstractTextDocumentLayout.PaintContext在PySide2中选区绘制失效求助

解决PySide2中QAbstractTextDocumentLayout.PaintContext缺失selections属性的选区高亮问题

针对PySide2环境下自定义日志控制台遇到的选区高亮失效问题,由于官方绑定遗漏了QAbstractTextDocumentLayout.PaintContext.selections属性,可通过手动绘制选区高亮的方式替代,具体实现如下:

核心思路

绕过缺失的selections属性,在paintEvent中先绘制QTextDocument的基础内容,再根据当前选区范围手动绘制高亮背景。需自行维护选区的起始/结束光标位置,并处理滚动偏移对选区坐标的影响。

代码实现(基于最小可复现示例修改)

from PySide2.QtWidgets import QWidget, QApplication
from PySide2.QtGui import QTextDocument, QPainter, QTextCursor, QPalette
from PySide2.QtCore import Qt, QRectF

class CustomTextWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.doc = QTextDocument()
        self.doc.setPlainText("\n".join([f"Line {i}" for i in range(1000)]))
        # 维护选区光标
        self.selection_cursor = QTextCursor(self.doc)
        self.selection_cursor.clearSelection()
        # 滚动偏移量
        self.scroll_offset = 0

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setClipRect(event.rect())
        # 绘制文档基础内容
        ctx = QTextDocument.PaintContext()
        self.doc.drawContents(painter, QRectF(0, -self.scroll_offset, self.width(), self.doc.size().height()))
        
        # 手动绘制选区高亮
        if self.selection_cursor.hasSelection():
            highlight_color = self.palette().color(QPalette.Highlight)
            painter.setBrush(highlight_color)
            painter.setPen(Qt.NoPen)
            
            # 遍历选区覆盖的所有文本块
            start_block = self.selection_cursor.block()
            end_block = self.selection_cursor.selectionEnd().block()
            current_block = start_block
            
            while current_block.isValid():
                block_layout = current_block.layout()
                block_rect = block_layout.boundingRect()
                block_y = current_block.blockNumber() * block_rect.height() - self.scroll_offset
                
                # 处理起始块的部分选区
                if current_block == start_block:
                    start_x = block_layout.positionForTextPosition(self.selection_cursor.positionInBlock()).x()
                    if current_block == end_block:
                        end_x = block_layout.positionForTextPosition(self.selection_cursor.selectionEnd().positionInBlock()).x()
                        painter.drawRect(start_x, block_y, end_x - start_x, block_rect.height())
                    else:
                        painter.drawRect(start_x, block_y, self.width() - start_x, block_rect.height())
                # 处理结束块的部分选区
                elif current_block == end_block:
                    end_x = block_layout.positionForTextPosition(self.selection_cursor.selectionEnd().positionInBlock()).x()
                    painter.drawRect(0, block_y, end_x, block_rect.height())
                # 处理中间完整文本块
                else:
                    painter.drawRect(0, block_y, self.width(), block_rect.height())
                
                current_block = current_block.next()

    def mousePressEvent(self, event):
        # 转换鼠标位置到文档坐标系
        doc_pos = event.pos() + Qt.Point(0, self.scroll_offset)
        cursor = QTextCursor(self.doc)
        cursor.setPosition(self.doc.documentLayout().hitTest(doc_pos.toPointF(), Qt.FuzzyHit))
        self.selection_cursor = cursor
        self.selection_cursor.beginEditBlock()
        self.update()

    def mouseMoveEvent(self, event):
        if self.selection_cursor.hasSelection():
            doc_pos = event.pos() + Qt.Point(0, self.scroll_offset)
            cursor = QTextCursor(self.doc)
            cursor.setPosition(self.doc.documentLayout().hitTest(doc_pos.toPointF(), Qt.FuzzyHit))
            self.selection_cursor.setPosition(cursor.position(), QTextCursor.KeepAnchor)
            self.update()

    def mouseReleaseEvent(self, event):
        self.selection_cursor.endEditBlock()
        self.update()

    def resizeEvent(self, event):
        self.doc.setTextWidth(self.width())
        super().resizeEvent(event)

if __name__ == "__main__":
    app = QApplication([])
    w = CustomTextWidget()
    w.resize(400, 300)
    w.show()
    app.exec_()

关键细节说明

  1. 选区维护:通过QTextCursor记录选区的起始和结束位置,在鼠标事件中实时更新选区范围。
  2. 坐标转换:滚动偏移量需叠加到鼠标位置,才能正确映射到文档中的实际位置。
  3. 分块绘制:针对跨多行的选区,逐个处理每个文本块的部分或完整高亮,确保选区显示准确。

这种方式完全绕过了PaintContext.selections的依赖,在PySide2中可正常实现选区高亮,同时保留原有的大文本处理性能优势。

内容的提问来源于stack exchange,提问作者Sneyek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 13:45:23