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

QPlainTextEdit选中单词高亮调用QTextCharFormat方法触发死循环如何解决

问题根因

你遇到的函数提前终止、无限循环既不是Qt内部bug,也不是异常触发,是代码逻辑和Qt信号机制共同导致的递归问题:

  1. 调用QTextCursor::mergeCharFormat/setCharFormat这类修改文档字符格式的接口时,会触发QTextDocument的内容变更信号,进而导致QPlainTextEdit重新校验选区状态,再次发射selectionChanged信号
  2. 原有代码使用textCursor()返回的编辑器当前光标对象做位置修改操作,会直接改变用户的选中状态,也会触发selectionChanged信号
  3. 槽函数还未执行完就被新的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实现高亮逻辑:

  1. 自定义继承自QSyntaxHighlighter的高亮类,绑定到编辑器的document()上
  2. 在TextEditor中增加成员变量存储当前要高亮的关键词
  3. selectionChanged触发时只更新关键词,调用rehighlight()触发重绘即可,完全不会出现信号递归问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 20:15:02