自定义补全器示例

自定义补全器示例演示了如何基于模型提供的数据为输入小部件提供字符串补全功能。该补全器根据用户输入的前三个字符弹出可能的单词建议,并将用户选择的单词通过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() 和一个私有的实例 QCompleterc

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

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,来完成单词的输入。它在使用 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

这两个值的 difference 是 extra,它是 3。这意味着从右边数最后三个字符 "U","A" 和 "L" 将由 tc 插入。

函数 textUnderCursor() 使用一个 QTextCursortc,来选择光标下的单词并返回它。

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_EnterQt::Key_ReturnQt::Key_EscapeQt::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 用于填充 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 中。我们在填充 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

© 2024 Qt 公司有限公司。本文件中的文档贡献均为各自所有者的版权。所提供的文档根据免费软件基金会发布的版本1.3的条款,在GNU自由文档许可证下许可。Qt及其相关标志是芬兰及/或在其他全球国家的The Qt Company Ltd.的商标。所有其他商标均为各自所有者的财产。