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

PySide6调用QTextLayout.setFormats高亮文本时文本块不可见问题

如何正确使用setFormats?

问题描述

程序在QMainWindow中嵌入了QTextEdit控件,需求如下:

  • 查找文本中的单词import
  • 通过block.layout.setFormats方法将匹配到的单词高亮为红色
  • 要求外观修改不录入撤销-重做历史,且不使用QSyntaxHighlighter实现

当前遇到异常:找到import单词并执行setFormats调用后,对应的文本块变为不可见,不清楚问题成因。

复现代码

from PySide6 import QtWidgets, QtCore, QtGui


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.central_widget = QtWidgets.QWidget(self)
        self.central_widget.setLayout(QtWidgets.QVBoxLayout(self.central_widget))
        self.text_editor = QtWidgets.QTextEdit(self.central_widget)
        self.central_widget.layout().addWidget(self.text_editor)
        self.setCentralWidget(self.central_widget)
        self.text_editor.setFont(QtGui.QFont('Arial', 14))
        self.text_editor.textChanged.connect(self.text_changed)

    @QtCore.Slot()
    def text_changed(self):
        word = 'import'
        text_cursor_before_find_op = self.text_editor.textCursor()

        self.text_editor.moveCursor(QtGui.QTextCursor.MoveOperation.Start)
        found = self.text_editor.find(word)
        if found:
            text_cursor = self.text_editor.textCursor()
            text_cursor.setPosition(text_cursor.position())
            block = text_cursor.block()
            position_in_block = text_cursor.positionInBlock() - len(word)
            format_range = QtGui.QTextLayout.FormatRange()
            format_range.start = position_in_block
            format_range.length = len(word)
            format_range.format = self.text_editor.currentCharFormat()
            format_range.format.setForeground(QtGui.QColor('#FF0000'))
            formats = [format_range]
            block.layout().setFormats(formats)
            print(position_in_block,
                  repr(block.text()),
                  (format_range.start, format_range.length, format_range.format),
                  block.isValid(), block.isVisible(),
                  sep='\n', end='\n\n')

        self.text_editor.setTextCursor(text_cursor_before_find_op)


app = QtWidgets.QApplication()
window = MainWindow()
window.show()
app.exec()

问题原因

文本块不可见的核心原因是对setFormats接口逻辑理解错误:

  • QTextLayout.setFormats()不是追加格式,而是全量替换当前文本块的所有渲染格式。原代码只传入了匹配到的import单词对应的格式范围,块内其余文本没有对应的格式配置,Qt渲染时会将无格式的文本判定为透明,最终整个文本块看起来完全消失。
  • 额外逻辑缺陷:textChanged信号触发时,文档的布局排版还未完成,此时直接设置的格式会被后续的默认布局流程覆盖,无法持久生效。

修正方案

  1. 调用setFormats前先获取文本块现有的全部格式列表,在原有格式基础上追加自定义高亮格式,禁止直接传单一格式段覆盖全块
  2. 将格式设置逻辑通过QTimer.singleShot(0, ...)投递到事件循环队列,等当前文本变更的布局流程完成后再执行格式设置
  3. 每次设置新格式前,先移除之前添加的自定义高亮格式,避免格式残留
  4. 设置完格式后主动触发对应文本块和视图的重绘,保证渲染更新

修正后可运行代码

from PySide6 import QtWidgets, QtCore, QtGui


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.central_widget = QtWidgets.QWidget(self)
        self.central_widget.setLayout(QtWidgets.QVBoxLayout(self.central_widget))
        self.text_editor = QtWidgets.QTextEdit(self.central_widget)
        self.central_widget.layout().addWidget(self.text_editor)
        self.setCentralWidget(self.central_widget)
        self.text_editor.setFont(QtGui.QFont('Arial', 14))
        self.target_word = 'import'
        self.highlight_color = QtGui.QColor('#FF0000')
        self.text_editor.textChanged.connect(self.schedule_highlight)

    def schedule_highlight(self):
        # 等待当前轮次布局完成后再执行高亮,避免格式被覆盖
        QtCore.QTimer.singleShot(0, self.apply_highlight)

    def apply_highlight(self):
        original_cursor = self.text_editor.textCursor()
        doc = self.text_editor.document()
        block = doc.begin()
        word_len = len(self.target_word)

        while block.isValid():
            layout = block.layout()
            # 过滤掉之前添加的旧高亮,保留原生格式
            valid_formats = []
            for fr in layout.formats():
                if fr.format.foreground().color() != self.highlight_color:
                    valid_formats.append(fr)

            # 遍历当前块匹配所有目标单词
            block_text = block.text()
            search_pos = 0
            while True:
                match_pos = block_text.find(self.target_word, search_pos)
                if match_pos == -1:
                    break
                # 构造高亮格式段
                hl_range = QtGui.QTextLayout.FormatRange()
                hl_range.start = match_pos
                hl_range.length = word_len
                hl_fmt = QtGui.QTextCharFormat()
                hl_fmt.setForeground(self.highlight_color)
                hl_range.format = hl_fmt
                valid_formats.append(hl_range)
                search_pos = match_pos + word_len

            # 全量设置格式
            layout.setFormats(valid_formats)
            block = block.next()

        # 恢复光标位置,更新视图
        self.text_editor.setTextCursor(original_cursor)
        self.text_editor.viewport().update()


app = QtWidgets.QApplication()
window = MainWindow()
window.show()
app.exec()

该实现的格式修改直接作用于渲染层,不会修改文档底层内容,因此不会进入撤销/重做栈,完全符合需求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 05:57:12