自定义完成示例

自定义补全器示例展示了如何根据模型提供的数据为输入 widget 提供字符串补全功能。填词器会根据用户输入的前三个字符弹出可能的单词建议,用户选择的单词会使用QTextCursor 插入TextEdit

设置资源文件

自定义补全器示例需要一个资源文件wordlist.txt,该文件包含一个单词列表,可帮助QCompleter 补全单词。该文件包含以下内容:

<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
   <file>resources/wordlist.txt</file>
</qresource>
</RCC>

TextEdit 类定义

TextEdit 类是QTextEdit 的子类,带有自定义的insertCompletion() 插槽,并重新实现了keyPressEvent() 和focusInEvent() 函数。TextEdit 还包含一个私有函数textUnderCursor()QCompleter 的私有实例c

class TextEdit : public QTextEdit
{
    Q_OBJECT

public:
    TextEdit(QWidget *parent = nullptr);
    ~TextEdit();

    void setCompleter(QCompleter *c);
    QCompleter *completer() const;

protected:
    void keyPressEvent(QKeyEvent *e) override;
    void focusInEvent(QFocusEvent *e) override;

private slots:
    void insertCompletion(const QString &completion);

private:
    QString textUnderCursor() const;

private:
    QCompleter *c = nullptr;
};

TextEdit 类的实现

TextEdit 的构造函数构造了一个带有父类的TextEdit ,并初始化了c 。使用setPlainText() 函数在TextEdit 对象上显示使用 completter 的说明。

TextEdit::TextEdit(QWidget *parent)
    : QTextEdit(parent)
{
    setPlainText(tr("This TextEdit provides autocompletions for words that have more than"
                    " 3 characters. You can trigger autocompletion using ") +
                    QKeySequence("Ctrl+E").toString(QKeySequence::NativeText));
}

此外,TextEdit 还包含一个默认的析构函数:

TextEdit::~TextEdit()
{
}

setCompleter() 函数接受一个completer 并对其进行设置。我们使用if (c) 来检查c 是否已被初始化。如果已经初始化,则调用QObject::disconnect() 函数断开信号与槽的连接。这样做是为了确保没有先前的完成程序对象仍然连接到插槽。

void TextEdit::setCompleter(QCompleter *completer)
{
    if (c)
        c->disconnect(this);

    c = completer;

    if (!c)
        return;

    c->setWidget(this);
    c->setCompletionMode(QCompleter::PopupCompletion);
    c->setCaseSensitivity(Qt::CaseInsensitive);
    QObject::connect(c, QOverload<const QString &>::of(&QCompleter::activated),
                     this, &TextEdit::insertCompletion);
}

然后,我们将ccompleter 进行实例化,并将其设置为TextEdit 的部件。完成模式和大小写敏感性也已设置,然后我们将activated() 信号连接到insertCompletion() 插槽。

completer() 函数是一个 getter 函数,返回c

QCompleter *TextEdit::completer() const
{
    return c;
}

根据wordlist.txt 的内容,补全器会弹出可用的选项,但文本光标负责根据用户选择的单词填写缺失的字符。

假设用户输入了 "ACT",并接受了补全器建议的 "ACTUAL"。然后,completion 字符串将通过完成器的activated() 信号发送到insertCompletion()

insertCompletion() 函数负责使用QTextCursor 对象tc 完成单词。在使用tc 插入额外字符完成单词之前,它会进行验证,以确保完成者的部件是TextEdit

void TextEdit::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);
}

下图说明了这一过程:

completion.length() = 6

c->completionPrefix().length()=3

这两个值之间的差值是extra ,即 3。这意味着右边的最后三个字符 "U"、"A "和 "L "将由tc 插入。

textUnderCursor() 函数使用QTextCursor,tc 来选择光标下的单词并返回。

QString TextEdit::textUnderCursor() const
{
    QTextCursor tc = textCursor();
    tc.select(QTextCursor::WordUnderCursor);
    return tc.selectedText();
}

TextEdit 类重新实现了focusInEvent() 函数,该函数是一个事件处理程序,用于接收部件的键盘焦点事件。

void TextEdit::focusInEvent(QFocusEvent *e)
{
    if (c)
        c->setWidget(this);
    QTextEdit::focusInEvent(e);
}

重新实现的keyPressEvent() 会忽略Qt::Key_Enter,Qt::Key_Return,Qt::Key_Escape,Qt::Key_TabQt::Key_Backtab 等按键事件,以便完成程序处理这些事件。

如果有一个激活的完成器,我们就无法处理快捷键 Ctrl+E。

void TextEdit::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;
       }
    }

    const bool isShortcut = (e->modifiers().testFlag(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().testFlag(Qt::ControlModifier) ||
                             e->modifiers().testFlag(Qt::ShiftModifier);
    if (!c || (ctrlOrShift && e->text().isEmpty()))
        return;

    static QString eow("~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="); // end of word
    const 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); // popup it up!
}

最后,我们弹出完成程序。

MainWindow 类定义

MainWindow 类是QMainWindow 的子类,实现了一个私有槽about() 。该类还有两个私有函数createMenu()modelFromFile() ,以及QCompleterTextEdit 的私有实例。

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);

private slots:
    void about();

private:
    void createMenu();
    QAbstractItemModel *modelFromFile(const QString& fileName);

    QCompleter *completer = nullptr;
    TextEdit *completingTextEdit;
};

MainWindow 类的实现

构造函数通过父类构造一个MainWindow ,并初始化completer 。它还实例化一个TextEdit ,并设置其完成器。从modelFromFile() 获取的QStringListModel 用于填充completerMainWindow 的中心部件设置为TextEdit ,大小设置为 500 x 300。

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    createMenu();

    completingTextEdit = new TextEdit;
    completer = new QCompleter(this);
    completer->setModel(modelFromFile(":/resources/wordlist.txt"));
    completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
    completer->setCaseSensitivity(Qt::CaseInsensitive);
    completer->setWrapAround(false);
    completingTextEdit->setCompleter(completer);

    setCentralWidget(completingTextEdit);
    resize(500, 300);
    setWindowTitle(tr("Completer"));
}

createMenu() 函数创建了 "文件 "和 "帮助 "菜单所需的QAction 对象,其triggered() 信号分别连接到quit()about()aboutQt() 插槽。

void MainWindow::createMenu()
{
    QAction *exitAction = new QAction(tr("Exit"), this);
    QAction *aboutAct = new QAction(tr("About"), this);
    QAction *aboutQtAct = new QAction(tr("About Qt"), this);

    connect(exitAction, &QAction::triggered, qApp, &QApplication::quit);
    connect(aboutAct, &QAction::triggered, this, &MainWindow::about);
    connect(aboutQtAct, &QAction::triggered, qApp, &QApplication::aboutQt);

    QMenu *fileMenu = menuBar()->addMenu(tr("File"));
    fileMenu->addAction(exitAction);

    QMenu *helpMenu = menuBar()->addMenu(tr("About"));
    helpMenu->addAction(aboutAct);
    helpMenu->addAction(aboutQtAct);
}

modelFromFile() 函数接受fileName ,并尝试将该文件的内容提取到QStringListModel 中。我们在填充QStringListwords 时显示Qt::WaitCursor ,并在完成后恢复鼠标光标。

QAbstractItemModel *MainWindow::modelFromFile(const QString& fileName)
{
    QFile file(fileName);
    if (!file.open(QFile::ReadOnly))
        return new QStringListModel(completer);

#ifndef QT_NO_CURSOR
    QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
#endif
    QStringList words;

    while (!file.atEnd()) {
        QByteArray line = file.readLine();
        if (!line.isEmpty())
            words << QString::fromUtf8(line.trimmed());
    }

#ifndef QT_NO_CURSOR
    QGuiApplication::restoreOverrideCursor();
#endif
    return new QStringListModel(words, completer);
}

about() 函数提供了有关自定义完成程序示例的简要说明。

void MainWindow::about()
{
    QMessageBox::about(this, tr("About"), tr("This example demonstrates the "
        "different features of the QCompleter class."));
}

main() 函数

main() 函数实例化MainWindow 并调用show() 函数。

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MainWindow window;
    window.show();
    return app.exec();
}

示例项目 @ code.qt.io

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.