本页内容

自定义补全示例

“自定义补全器”示例演示了如何基于模型提供的数据,为输入控件提供字符串补全功能。该补全器会根据用户输入的前三个字符弹出可能的单词建议,并通过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 对象上显示使用补全器的说明。

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()函数以断开信号与插槽的连接。这是为了确保没有之前的completer对象仍连接到该插槽。

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);
}

随后,我们使用completer 实例化c ,并将其设为TextEdit 的小部件。同时设置完成模式和区分大小写选项,然后将activated()信号连接至insertCompletion() 槽。

completer() 函数是一个获取函数,返回c 。

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

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

假设用户输入“ACT”并接受补全器建议的“ACTUAL”。随后,补全器的activated()信号会将字符串completion 发送给insertCompletion() 。

insertCompletion() 函数负责使用QTextCursor 对象(tc )来完成单词。它会进行验证,确保补全器的控件处于TextEdit 状态,然后使用tc 插入额外字符以完成单词。

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);
}

下图说明了这一过程:

当用户输入“ACT”时,建议补全为“ACTUAL”

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_Tab 和Qt::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() ,以及QCompleter 和TextEdit 的私有实例。

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 用于填充completer 。将MainWindow 的中心控件设置为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 中。在填充QStringList 和words 时,我们会显示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

© 2026 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.