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
相关产品推荐
相关产品推荐

