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

PyQt6中QTextBlock.setVisible性能问题及格式化文本隐藏方案咨询

PyQt6中高效隐藏带格式文本行的方案

问题描述

我编写了以下PoC代码,两个按钮实现相同的行筛选功能:纯文本复制的方式几乎瞬间完成,但调用block.setVisible()的方式耗时约10秒。由于后续文本会包含格式,纯文本复制方案无法满足需求,特此咨询:在保留文本格式的前提下,高效隐藏/移除指定行的合理方法是什么?

from PyQt6.QtWidgets import QApplication,QWidget,QTextEdit,QVBoxLayout,QPushButton
from PyQt6.QtGui import QTextBlockUserData
import sys
class UserData(QTextBlockUserData):
    """存储每个文本块的事件信息(行号、时间戳、级别)"""

    def __init__(self, level=-1, line=-1, ts=-1):
        super(UserData, self).__init__()
        self.level = level
        self.line = line
        self.timestamp = ts

    def set_event_details(self, line, level, ts):
        """设置事件信息(行号、时间戳、级别)"""
        self.level = level
        self.line = line
        self.timestamp = ts
    def get_level(self):
        return UserData.level

    def get_timestamp(self):
        return UserData.timestamp

    def get_line(self):
        return UserData.line


class TextEditDemo(QWidget):

    def __init__(self,parent=None):
        super().__init__(parent)

        self.setWindowTitle("QTextEdit")
        self.resize(600,800)

        self.text_edit = QTextEdit()
        self.btnPress1 = QPushButton("Button 1")
        self.btnPress2 = QPushButton("Button 2")

        layout = QVBoxLayout()
        layout.addWidget(self.text_edit)
        layout.addWidget(self.btnPress1)
        layout.addWidget(self.btnPress2)
        self.setLayout(layout)

        demo = [['2022.11.08 11: 54:01.270', 2,
                 'TLS:127.0.0.1:57655 SSL handshake ready.'],
                ['2022.11.08 11: 54:01.301', 1, 'Test'],
                ['2022.11.08 11: 54:01.301', 0,
                 'File corrupted'],
                ['2022.11.08 11: 54:01.302', 1,
                 'Test 2'],
                ['2022.11.08 11: 54:01.324', 2,
                 'thread exited.']]
        for i in range(1000):
            for item in demo:
                self.text_edit.append(item[0] +f' level {item[1]} ' + item[2])

        text_document = self.text_edit.document()
        for i in range(text_document.blockCount()):
            block = text_document.findBlockByNumber(i)
            temp = UserData()
            temp.set_event_details(i, demo[i%5][1], demo[i%5][0])
            block.setUserData(temp)

        self.btnPress1.clicked.connect(self.btnPress1_Clicked)
        self.btnPress2.clicked.connect(self.btnPress2_Clicked)

    def refresh_text_box(self):
        QApplication.processEvents()  # 更新PyQt界面


    def btnPress1_Clicked(self):
        text_document = self.text_edit.document()
        for i in range(text_document.blockCount()):
            block = text_document.findBlockByNumber(i)
            if block.userData().level > 1:
                block.setVisible(False)
                text_document.markContentsDirty(block.position(), block.length())
            else:
                block.setVisible(True)
                text_document.markContentsDirty(block.position(), block.length())
        self.text_edit.update()



    def btnPress2_Clicked(self):
            text = ''
            text_document = self.text_edit.document()
            for i in range(text_document.blockCount()):
                block = text_document.findBlockByNumber(i)
                if block.userData().level <= 1:
                    text += block.text() + '\n'
            self.text_edit.setPlainText(text)



if __name__ == '__main__':
    app = QApplication([])
    win = TextEditDemo()
    win.show()
    app.exec()

解决方案

1. 修复代码bug+优化UI重绘逻辑

首先你的UserData类存在严重bug:所有get方法返回的是类属性而非实例属性,这会导致level判断完全错误,同时也是效率损耗的潜在因素,先修复:

def get_level(self):
    return self.level  # 替换原有的UserData.level

def get_timestamp(self):
    return self.timestamp  # 替换原有的UserData.timestamp

def get_line(self):
    return self.line  # 替换原有的UserData.line

然后优化btnPress1_Clicked方法,暂停UI更新后批量处理,避免频繁重绘:

def btnPress1_Clicked(self):
    text_document = self.text_edit.document()
    # 禁用文本编辑的实时更新,避免中间多次重绘
    self.text_edit.setUpdatesEnabled(False)
    
    for i in range(text_document.blockCount()):
        block = text_document.findBlockByNumber(i)
        user_data = block.userData()
        if user_data and user_data.level > 1:
            block.setVisible(False)
        else:
            block.setVisible(True)
    
    # 一次性标记整个文档为脏区,触发一次重绘
    text_document.markContentsDirty(0, text_document.characterCount())
    self.text_edit.setUpdatesEnabled(True)
    self.text_edit.update()

这种优化能大幅降低block.setVisible()方式的耗时,同时完全保留文本格式。

2. 使用QTextDocumentFragment批量构建带格式内容

如果需要和纯文本方案接近的速度,可以用QTextDocumentFragment复制带格式的内容,批量构建后替换文本编辑的内容:

from PyQt6.QtGui import QTextDocumentFragment, QTextCursor

def btnPress1_Clicked(self):
    text_document = self.text_edit.document()
    # 创建空的文档片段用于存储筛选后的内容
    target_fragment = QTextDocumentFragment()
    cursor = QTextCursor(target_fragment.begin())
    
    for i in range(text_document.blockCount()):
        block = text_document.findBlockByNumber(i)
        user_data = block.userData()
        if user_data and user_data.level <= 1:
            # 复制整个块的内容(包含所有格式)到片段
            cursor.insertFragment(QTextDocumentFragment(block))
            cursor.insertBlock()  # 保持行分隔
    
    # 替换文本编辑内容,完全保留格式
    self.text_edit.setDocumentFragment(target_fragment)

这种方式的速度和纯文本方案几乎一致,同时完美保留文本格式,适合频繁切换筛选条件的场景。

3. 自定义文本布局(进阶方案)

如果需要动态隐藏行且不修改文档结构,可以自定义QAbstractTextDocumentLayout,在布局阶段跳过不需要显示的块。该方案实现复杂度较高,适合对性能要求极高的场景,需要重写布局相关的方法。


内容的提问来源于stack exchange,提问作者Balog Dániel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 14:32:20