我想像Qt Creator这样在此处选择的单词后面显示一个矩形:

我正在尝试QSyntaxHighlighter的示例。我能够根据关键字模式更改样式。我想要用于自定义自动完成列表的图形或小部件。

最佳答案

对于自动补全,请遵循 Custom Completer Example Completer Example

下面的代码遵循第一个,我毫不掩饰地将其公然复制并集成到BackgroundHighlighter类和main.cpp中。

这个答案将在一个项目中包含五个文件以及一个Qt资源文件。

  • highlighter.h(语法的突出显示类)
  • highlighter.cpp
  • backgroundHighlighter.h(BackgroundHighlighter类)
  • backgroundHighlighter.cpp
  • main.cpp
  • res.qrc(可选,不需要,您可以对文本进行硬编码)
  • res(目录)(可选)
  • |- symbols.txt(可选,您可以设置自己的默认文本)
  • |- wordlist.txt(可选,从example复制,但是您可以使用自己的以行分隔的单词列表,并在main.cpp中使用 QStringListModel 进行设置)

  • 请注意,可以在Qt Syntax Highlighter Example中找到(1)和(2)的Highlighter类的实现。我将其实现留给读者练习。

    在调用BackgroundHighlighter类时,可以向其传递文件名以从文件中加载文本。 (这不在OP的规范中,但是由于我要测试的大量文本,因此实现起来很方便。)

    还要注意,我将 Custom Completer Example 集成到该类中。

    这是backgroundHighlighter.h(3)(〜45行,带有completer的〜60行):
    #ifndef BACKGROUNDHIGHLIGHTER_H
    #define BACKGROUNDHIGHLIGHTER_H
    
    #include <QtWidgets>
    #include <QtGui>
    
    //  this is the file to your highlighter
    #include "myhighlighter.h"
    
    class BackgroundHighlighter : public QTextEdit
    {
        Q_OBJECT
    public:
        BackgroundHighlighter(const QString &fileName = QString(), QWidget *parent = nullptr);
    
        void loadFile(const QString &fileName);
    
        void setCompleter(QCompleter *completer);
        QCompleter *completer() const;
    
    protected:
        void keyPressEvent(QKeyEvent *e) override;
        void focusInEvent(QFocusEvent *e) override;
    
    public slots:
        void onCursorPositionChanged();
    
    private slots:
        void insertCompletion(const QString &completion);
    
    private:
        //  this is your syntax highlighter
        Highlighter *syntaxHighlighter;
    
        //  stores the symbol being highlighted
        QString highlightSymbol;
    
        //  stores the position (front of selection) where the cursor was originally placed
        int mainHighlightPosition;
    
        //  stores character formats to be used
        QTextCharFormat mainFmt;           //  refers to format block directly under the cursor
        QTextCharFormat subsidiaryFmt;     //  refers to the formatting blocks on matching words
        QTextCharFormat defaultFmt;        //  refers to the default format of the **entire** document which will be used in resetting the format
    
        void setWordFormat(const int &position, const QTextCharFormat &format);
        void runHighlight();
        void clearHighlights();
        void highlightMatchingSymbols(const QString &symbol);
    
        //  completer, copied from example
        QString textUnderCursor() const;
        QCompleter *c;
    
    };
    
    #endif // BACKGROUNDHIGHLIGHTER_H
    

    这是backgroundHighlighter.cpp(4)(〜160行,带有completer的〜250行):
    #include "backgroundhighlighter.h"
    
    #include <QDebug>
    
    //  constructor
    BackgroundHighlighter::BackgroundHighlighter(const QString &fileName, QWidget *parent) :
        QTextEdit(parent)
    {
        //  I like Monaco
        setFont(QFont("Monaco"));
        setMinimumSize(QSize(500, 200));
    
        //  load initial text from a file OR from a hardcoded default
        if (!fileName.isEmpty())
            loadFile(fileName);
        else
        {
            QString defaultText = "This is a default text implemented by "
                                  "a stackoverflow user. Please upvote the answer "
                                  "at https://stackoverflow.com/a/53351512/10239789.";
    
            setPlainText(defaultText);
        }
    
        //  set the highlighter here
        QTextDocument *doc = document();
        syntaxHighlighter = new Highlighter(doc);
    
        //  TODO change brush/colours to match theme
        mainFmt.setBackground(Qt::yellow);
        subsidiaryFmt.setBackground(Qt::lightGray);
        defaultFmt.setBackground(Qt::white);
    
        //  connect the signal to our handler
        connect(this, &QTextEdit::cursorPositionChanged, this, &BackgroundHighlighter::onCursorPositionChanged);
    }
    
    //  convenience function for reading a file
    void BackgroundHighlighter::loadFile(const QString &fileName)
    {
        QFile file(fileName);
        if (!file.open(QIODevice::ReadOnly))
            return;
    
        //  the file could be in Plain Text OR Html
        setText(file.readAll());
    }
    
    void BackgroundHighlighter::setCompleter(QCompleter *completer)
    {
        if (c)
            QObject::disconnect(c, 0, this, 0);
    
        c = completer;
    
        if (!c)
            return;
    
        c->setWidget(this);
        c->setCompletionMode(QCompleter::PopupCompletion);
        c->setCaseSensitivity(Qt::CaseInsensitive);
        QObject::connect(c, SIGNAL(activated(QString)),
                         this, SLOT(insertCompletion(QString)));
    }
    
    QCompleter *BackgroundHighlighter::completer() const
    {
        return c;
    }
    
    void BackgroundHighlighter::keyPressEvent(QKeyEvent *e)
    {
        if (c && c->popup()->isVisible()) {
            // The following keys are forwarded by the completer to the widget
           switch (e->key()) {
           case Qt::Key_Enter:
           case Qt::Key_Return:
           case Qt::Key_Escape:
           case Qt::Key_Tab:
           case Qt::Key_Backtab:
                e->ignore();
                return; // let the completer do default behavior
           default:
               break;
           }
        }
    
        bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_E); // CTRL+E
        if (!c || !isShortcut) // do not process the shortcut when we have a completer
            QTextEdit::keyPressEvent(e);
    
        const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
       if (!c || (ctrlOrShift && e->text().isEmpty()))
           return;
    
       static QString eow("~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="); // end of word
       bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
       QString completionPrefix = textUnderCursor();
    
       if (!isShortcut && (hasModifier || e->text().isEmpty()|| completionPrefix.length() < 3
                         || eow.contains(e->text().right(1)))) {
           c->popup()->hide();
           return;
       }
    
       if (completionPrefix != c->completionPrefix()) {
           c->setCompletionPrefix(completionPrefix);
           c->popup()->setCurrentIndex(c->completionModel()->index(0, 0));
       }
       QRect cr = cursorRect();
       cr.setWidth(c->popup()->sizeHintForColumn(0)
                   + c->popup()->verticalScrollBar()->sizeHint().width());
       c->complete(cr); // pop it up!
    }
    
    void BackgroundHighlighter::focusInEvent(QFocusEvent *e)
    {
        if (c)
            c->setWidget(this);
        QTextEdit::focusInEvent(e);
    }
    
    //  convenience function for setting a `charFmt` at a `position`
    void BackgroundHighlighter::setWordFormat(const int &position, const QTextCharFormat &charFmt)
    {
        QTextCursor cursor = textCursor();
        cursor.setPosition(position);
        cursor.select(QTextCursor::WordUnderCursor);
        cursor.setCharFormat(charFmt);
    }
    
    //  this will handle the `QTextEdit::cursorPositionChanged()` signal
    void BackgroundHighlighter::onCursorPositionChanged()
    {
        //  if cursor landed on different format, the `currentCharFormat` will be changed
        //  we need to change it back to white
        setCurrentCharFormat(defaultFmt);
    
        //  this is the function you're looking for
        runHighlight();
    }
    
    void BackgroundHighlighter::insertCompletion(const QString &completion)
    {
        if (c->widget() != this)
            return;
        QTextCursor tc = textCursor();
        int extra = completion.length() - c->completionPrefix().length();
        tc.movePosition(QTextCursor::Left);
        tc.movePosition(QTextCursor::EndOfWord);
        tc.insertText(completion.right(extra));
        setTextCursor(tc);
    }
    
    QString BackgroundHighlighter::textUnderCursor() const
    {
        QTextCursor tc = textCursor();
        tc.select(QTextCursor::WordUnderCursor);
        return tc.selectedText();
    }
    
    /**
     * BRIEF
     * Check if new highlighting is needed
     * Clear previous highlights
     * Check if the word under the cursor is a symbol (i.e. matches ^[A-Za-z0-9_]+$)
     * Highlight all relevant symbols
     */
    void BackgroundHighlighter::runHighlight()
    {
        //  retrieve cursor
        QTextCursor cursor = textCursor();
    
        //  retrieve word under cursor
        cursor.select(QTextCursor::WordUnderCursor);
        QString wordUnder = cursor.selectedText();
        qDebug() << "Word Under Cursor:" << wordUnder;
    
        //  get front of cursor, used later for storing in `highlightPositions` or `mainHighlightPosition`
        int cursorFront = cursor.selectionStart();
    
        //  if the word under cursor is the same, then save time
        //  by skipping the process
        if (wordUnder == highlightSymbol)
        {
            //  switch formats
            setWordFormat(mainHighlightPosition, subsidiaryFmt);    //  change previous main to subsidiary
            setWordFormat(cursorFront, mainFmt);                  //  change position under cursor to main
    
            //  update main position
            mainHighlightPosition = cursorFront;
    
            //  jump the gun
            return;
        }
    
        //  clear previous highlights
        if (mainHighlightPosition != -1)
            clearHighlights();
    
        //  check if selected word is a symbol
        if (!wordUnder.contains(QRegularExpression("^[A-Za-z0-9_]+$")))
        {
            qDebug() << wordUnder << "is not a symbol!";
            return;
        }
    
        //  set the highlight symbol
        highlightSymbol = wordUnder;
    
        //  store the cursor position to check later
        mainHighlightPosition = cursorFront;
    
        //  highlight all relevant symbols
        highlightMatchingSymbols(wordUnder);
    
        qDebug() << "Highlight done\n\n";
    }
    
    //  clear previously highlights
    void BackgroundHighlighter::clearHighlights()
    {
        QTextCursor cursor = textCursor();
    
        //  wipe the ENTIRE document with the default background, this should be REALLY fast
        //  WARNING: this may have unintended consequences if you have other backgrounds you want to keep
        cursor.select(QTextCursor::Document);
        cursor.setCharFormat(defaultFmt);
    
        //  reset variables
        mainHighlightPosition = -1;
        highlightSymbol.clear();
    }
    
    //  highlight all matching symbols
    void BackgroundHighlighter::highlightMatchingSymbols(const QString &symbol)
    {
        //  highlight background of congruent symbols
        QString docText = toPlainText();
    
        //  use a regex with \\b to look for standalone symbols
        QRegularExpression regexp("\\b" + symbol + "\\b");
    
        //  loop through all matches in the text
        int matchPosition = docText.indexOf(regexp);
        while (matchPosition != -1)
        {
            //  if the position
            setWordFormat(matchPosition, matchPosition == mainHighlightPosition ? mainFmt : subsidiaryFmt);
    
            //  find next match
            matchPosition = docText.indexOf(regexp, matchPosition + 1);
        }
    }
    

    最后,这是main.cpp(5)(〜10行,〜45行,带有completer)
    #include <QApplication>
    #include <backgroundhighlighter.h>
    
    QAbstractItemModel *modelFromFile(const QString& fileName, QCompleter *completer)
    {
        QFile file(fileName);
        if (!file.open(QFile::ReadOnly))
            return new QStringListModel(completer);
    
    #ifndef QT_NO_CURSOR
        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
    #endif
        QStringList words;
    
        while (!file.atEnd()) {
            QByteArray line = file.readLine();
            if (!line.isEmpty())
                words << line.trimmed();
        }
    
    #ifndef QT_NO_CURSOR
        QApplication::restoreOverrideCursor();
    #endif
    
        return new QStringListModel(words, completer);
    }
    
    int main(int argc, char *argv[]) {
        QApplication a(argc, argv);
    
        BackgroundHighlighter bh(":/res/symbols.txt");
    
        QCompleter *completer = new QCompleter();
    
        completer->setModel(modelFromFile(":/res/wordlist.txt", completer));
    
        // use this and comment the above if you don't have or don't want to use wordlist.txt
        // QStringListModel *model = new QStringListModel(QStringList() << "aaaaaaa" << "aaaaab" << "aaaabb" << "aaacccc",
                                                   completer);
        // completer->setModel(model);
    
        completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
        completer->setCaseSensitivity(Qt::CaseInsensitive);
        completer->setWrapAround(false);
        bh.setCompleter(completer);
    
        bh.show();
    
        return a.exec();
    }
    

    res.qrc中添加/前缀,并从res/symbols.txt子目录中添加文件(res/wordlist.txtres/)。

    我已经用类似symbols.txt的文件进行了测试
    symbol1 symbol2 symbol3 symbol4 symbol5
    symbol1 symbol2 symbol3 symbol4 symbol5
    symbol1 symbol2 symbol3 symbol4 symbol5
    // ... ditto 500 lines
    

    大约需要1秒,这可能不理想(100ms可能更理想)。

    但是,您可能需要注意行数的增长。使用1000行的相同文本文件,该程序将开始花费大约。 3秒突出显示。

    请注意...我还没有完全优化 。只有当符号滚动到用户的 View 中时,可能会有更好的实现,即格式。这只是一个建议。我不知道如何实现。

    注释
  • 作为引用,我在github上附加了symbol.txt和wordlist.txt。
  • 如果要更改格式的背景色,请转到backgroundhighlighter.cpp的第27至29行。在那里,您可以看到我集中了格式设置。
  • BackgroundHighlighter::clearHighlights()可能会清除最初添加的所有背景突出显示,因为它会将整个文档的字符背景设置为默认格式。这可能是结果的意外结果。
  • 关于c++ - 如何在Qt中的QTextEdit中的文本后面或前景显示图形对象?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/53318233/

    10-10 18:26