QPlainTextEdit选中单词高亮调用QTextCharFormat方法触发死循环如何解决
问题根因
你遇到的函数提前终止、无限循环既不是Qt内部bug,也不是异常触发,是代码逻辑和Qt信号机制共同导致的递归问题:
- 调用
QTextCursor::mergeCharFormat/setCharFormat这类修改文档字符格式的接口时,会触发QTextDocument的内容变更信号,进而导致QPlainTextEdit重新校验选区状态,再次发射selectionChanged信号 - 原有代码使用
textCursor()返回的编辑器当前光标对象做位置修改操作,会直接改变用户的选中状态,也会触发selectionChanged信号 - 槽函数还未执行完就被新的
selectionChanged信号中断,重新进入槽函数,最终形成无限递归,表现为函数提前终止、反复触发
可行解决方案
快速修复现有代码
只需要做两处修改即可解决问题:一是临时阻塞信号避免递归,二是使用独立的QTextCursor操作格式,不修改用户当前的选中状态:
private: void selectChangeHandler() { // 临时阻塞当前控件的所有信号,避免修改操作重复触发selectionChanged const bool oldSignalState = blockSignals(true); auto currentCursor = textCursor(); if (currentCursor.selectionStart() >= currentCursor.selectionEnd()) { blockSignals(oldSignalState); // 退出前恢复信号状态 return; } auto plaintext = toPlainText(); // 修复原代码的边界越界问题:选区在文档开头/结尾时也不会出错 bool isWholeWord = true; if (currentCursor.selectionStart() > 0) { QChar prevChar = plaintext.at(currentCursor.selectionStart() - 1); if (prevChar.isLetterOrNumber()) isWholeWord = false; } if (currentCursor.selectionEnd() < plaintext.size()) { QChar nextChar = plaintext.at(currentCursor.selectionEnd()); if (nextChar.isLetterOrNumber()) isWholeWord = false; } if (!isWholeWord) { blockSignals(oldSignalState); return; } QString selectedText = currentCursor.selectedText(); if (selectedText.isEmpty() || selectedText.contains(QRegularExpression(R"([^a-zA-Z0-9])"))) { blockSignals(oldSignalState); return; } QTextCharFormat highlightFormat; highlightFormat.setBackground(Qt::green); size_t pos = 0; QRegExp matchReg(selectedText); // 用和文档绑定的独立cursor操作格式,不会修改用户当前的选中状态 QTextCursor opCursor(document()); int matchIndex = matchReg.indexIn(plaintext, pos); while (matchIndex >= 0) { opCursor.setPosition(matchIndex); opCursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1); opCursor.mergeCharFormat(highlightFormat); pos = matchIndex + (size_t)matchReg.matchedLength(); matchIndex = matchReg.indexIn(plaintext, pos); } // 所有操作完成后恢复信号 blockSignals(oldSignalState); }
更推荐的实现方案
如果需要避免修改文档原生格式(比如用户复制内容时不会带高亮背景),可以用QSyntaxHighlighter实现高亮逻辑:
- 自定义继承自
QSyntaxHighlighter的高亮类,绑定到编辑器的document()上 - 在
TextEditor中增加成员变量存储当前要高亮的关键词 selectionChanged触发时只更新关键词,调用rehighlight()触发重绘即可,完全不会出现信号递归问题。
内容的提问来源于stack exchange,提问作者Dex
相关产品推荐
相关产品推荐

