本页内容

Completer 示例

“Completer”示例演示了如何根据模型提供的数据,为输入控件提供字符串补全功能。

展示各种选项和设置的应用程序

本示例使用自定义项目模型 `FileSystemModel` 以及 `QCompleter ` 对象。`QCompleter ` 是一个基于项目模型提供补全功能的类。可以通过下拉列表选择模型类型、补全模式以及区分大小写设置。

资源文件

“Completer”示例需要一个资源文件来存储countries.txt和words.txt。该资源文件包含以下代码:

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

FileSystemModel 类定义

FileSystemModel 类是QFileSystemModel 类的子类,该类为本地文件系统提供数据模型。

class FileSystemModel : public QFileSystemModel
{
public:
    FileSystemModel(QObject *parent = nullptr);
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
};

该类仅包含一个构造函数和一个data() 函数,因为它仅用于使data() 能够为显示角色返回完整的文件路径;这与QFileSystemModel 中的data() 函数不同,后者仅返回文件夹路径而不返回驱动器标签。这一点在FileSystemModel 的实现中将进一步说明。

FileSystemModel 类的实现

FileSystemModel 类的构造函数用于将parent 传递给QFileSystemModel 。

FileSystemModel::FileSystemModel(QObject *parent)
    : QFileSystemModel(parent)
{
}

如前所述,为了使其在显示时返回完整的文件路径,我们重写了data() 函数。例如,使用QFileSystemModel 时,视图中会显示“Program Files”;而使用FileSystemModel 时,则会显示“C:\Program Files”。

QVariant FileSystemModel::data(const QModelIndex &index, int role) const
{
    if (role == Qt::DisplayRole && index.column() == 0) {
        QString path  = QDir::toNativeSeparators(filePath(index));
        if (path.endsWith(QDir::separator()))
            path.chop(1);
        return path;
    }

    return QFileSystemModel::data(index, role);
}

用于查找匹配项的Qt::EditRole (QCompleter 类所使用的)保持不变。

MainWindow 类定义

MainWindow 类是QMainWindow 的子类,并实现了五个私有槽——about() 、changeCase() 、changeMode() 、changeModel() 和changeMaxVisible() 。

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);

private slots:
    void about();
    void changeCase(int);
    void changeMode(int);
    void changeModel();
    void changeMaxVisible(int);

在MainWindow 类中,我们定义了两个私有函数:createMenu() 和modelFromFile() 。此外,我们还声明了所需的私有控件——三个QComboBox 对象、一个QCheckBox 、一个QCompleter 、一个QLabel 以及一个QLineEdit 。

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

    QComboBox *caseCombo = nullptr;
    QComboBox *modeCombo = nullptr;
    QComboBox *modelCombo = nullptr;
    QSpinBox *maxVisibleSpinBox = nullptr;
    QCheckBox *wrapCheckBox = nullptr;
    QCompleter *completer = nullptr;
    QLabel *contentsLabel = nullptr;
    QLineEdit *lineEdit = nullptr;
};

MainWindow 类的实现

MainWindow 的构造函数会创建一个带有父控件的MainWindow ,并初始化私有成员。随后调用createMenu() 函数。

我们创建了三个QComboBox 对象:modelComb 、modeCombo 和caseCombo 。默认情况下,modelCombo 设置为QFileSystemModel ,modeCombo 设置为“Filtered Popup”,caseCombo 设置为“Case Insensitive”。

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

    QWidget *centralWidget = new QWidget;

    QLabel *modelLabel = new QLabel;
    modelLabel->setText(tr("Model"));

    modelCombo = new QComboBox;
    modelCombo->addItem(tr("QFileSystemModel"));
    modelCombo->addItem(tr("QFileSystemModel that shows full path"));
    modelCombo->addItem(tr("Country list"));
    modelCombo->addItem(tr("Word list"));
    modelCombo->setCurrentIndex(0);

    QLabel *modeLabel = new QLabel;
    modeLabel->setText(tr("Completion Mode"));
    modeCombo = new QComboBox;
    modeCombo->addItem(tr("Inline"));
    modeCombo->addItem(tr("Filtered Popup"));
    modeCombo->addItem(tr("Unfiltered Popup"));
    modeCombo->setCurrentIndex(1);

    QLabel *caseLabel = new QLabel;
    caseLabel->setText(tr("Case Sensitivity"));
    caseCombo = new QComboBox;
    caseCombo->addItem(tr("Case Insensitive"));
    caseCombo->addItem(tr("Case Sensitive"));
    caseCombo->setCurrentIndex(0);

创建了maxVisibleSpinBox ,用于确定补全器中可见项的数量

随后配置wrapCheckBox 。该checkBox 用于确定completer 的setWrapAround()属性是启用还是禁用。

    QLabel *maxVisibleLabel = new QLabel;
    maxVisibleLabel->setText(tr("Max Visible Items"));
    maxVisibleSpinBox = new QSpinBox;
    maxVisibleSpinBox->setRange(3,25);
    maxVisibleSpinBox->setValue(10);

    wrapCheckBox = new QCheckBox;
    wrapCheckBox->setText(tr("Wrap around completions"));
    wrapCheckBox->setChecked(true);

我们实例化contentsLabel ,并将它的大小策略设置为fixed 。随后将下拉列表的activated()信号连接到各自的槽。

    contentsLabel = new QLabel;
    contentsLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);

    connect(modelCombo, &QComboBox::activated,
            this, &MainWindow::changeModel);
    connect(modeCombo, &QComboBox::activated,
            this, &MainWindow::changeMode);
    connect(caseCombo, &QComboBox::activated,
            this, &MainWindow::changeCase);
    connect(maxVisibleSpinBox, &QSpinBox::valueChanged,
            this, &MainWindow::changeMaxVisible);

lineEdit 设置完成后,我们使用QGridLayout 来排列所有控件。随后调用changeModel() 函数,以初始化completer 。

    lineEdit = new QLineEdit;

    QGridLayout *layout = new QGridLayout;
    layout->addWidget(modelLabel, 0, 0); layout->addWidget(modelCombo, 0, 1);
    layout->addWidget(modeLabel, 1, 0);  layout->addWidget(modeCombo, 1, 1);
    layout->addWidget(caseLabel, 2, 0);  layout->addWidget(caseCombo, 2, 1);
    layout->addWidget(maxVisibleLabel, 3, 0); layout->addWidget(maxVisibleSpinBox, 3, 1);
    layout->addWidget(wrapCheckBox, 4, 0);
    layout->addWidget(contentsLabel, 5, 0, 1, 2);
    layout->addWidget(lineEdit, 6, 0, 1, 2);
    centralWidget->setLayout(layout);
    setCentralWidget(centralWidget);

    changeModel();

    setWindowTitle(tr("Completer"));
    lineEdit->setFocus();
}

createMenu() 函数用于实例化填充fileMenu 和helpMenu 所需的QAction 对象。动作的triggered()信号被连接到各自的槽中。

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 ,并根据其内容进行处理。

我们首先验证file ,以确保其可在 QFile::ReadOnly 模式下打开。若验证失败,该函数将返回一个空的QStringListModel 。

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

随后,在将QStringList 对象words 填充为file 的内容之前,会先使用Qt::WaitCursor 覆盖鼠标光标。完成此操作后,我们将鼠标光标恢复原状。

#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

如前所述,资源文件包含两个文件——countries. txt和words.txt。如果file 读取的结果是words.txt,则返回一个QStringListModel ,其QStringList 为words ,父元素为completer 。

    if (!fileName.contains(QLatin1String("countries.txt")))
        return new QStringListModel(words, completer);

如果读取的file 文件是countries.txt,则需要一个QStandardItemModel ,该文件包含words.count() 行、2列,且其父文件为completer 。

    QStandardItemModel *m = new QStandardItemModel(words.count(), 2, completer);

countries.txt文件中的一行标准格式如下:

Norway NO

因此,要填充QStandardItemModel 对象m ,我们需要将国家名称及其代码拆分。完成此操作后,我们返回m 。

    for (int i = 0; i < words.count(); ++i) {
        QModelIndex countryIdx = m->index(i, 0);
        QModelIndex symbolIdx = m->index(i, 1);
        QString country = words.at(i).mid(0, words[i].length() - 2).trimmed();
        QString symbol = words.at(i).right(2);
        m->setData(countryIdx, country);
        m->setData(symbolIdx, symbol);
    }

    return m;
}

changeMode() 函数根据index 的值,设置completer 的模式。

void MainWindow::changeMode(int index)
{
    QCompleter::CompletionMode mode;
    if (index == 0)
        mode = QCompleter::InlineCompletion;
    else if (index == 1)
        mode = QCompleter::PopupCompletion;
    else
        mode = QCompleter::UnfilteredPopupCompletion;

    completer->setCompletionMode(mode);
}

changeModel() 函数会根据用户选择的模型来更改所使用的项目模型。

switch 语句用于根据modelCombo 的索引更改项目模型。如果case 为0,则使用未排序的QFileSystemModel ,该文件路径不包含驱动器标签。

void MainWindow::changeModel()
{
    delete completer;
    completer = new QCompleter(this);
    completer->setMaxVisibleItems(maxVisibleSpinBox->value());

    switch (modelCombo->currentIndex()) {
    default:
    case 0:
        { // Unsorted QFileSystemModel
            QFileSystemModel *fsModel = new QFileSystemModel(completer);
            fsModel->setRootPath(QString());
            completer->setModel(fsModel);
            contentsLabel->setText(tr("Enter file path"));
        }
        break;

请注意,我们创建该模型时将completer 设为父模型,这样可以让我们用新模型替换该模型。completer 将确保在向其分配新模型的瞬间,旧模型会被删除。

如果 `case ` 设为 1,我们将使用之前定义的 `DirModel `,从而生成文件的完整路径。

    case 1:
        {   // FileSystemModel that shows full paths
            FileSystemModel *fsModel = new FileSystemModel(completer);
            completer->setModel(fsModel);
            fsModel->setRootPath(QString());
            contentsLabel->setText(tr("Enter file path"));
        }
        break;

当 `case ` 设为 2 时,我们将尝试对国家名称进行补全。这需要一个 `QTreeView ` 对象,即 `treeView`。国家名称将从`countries.txt` 中提取,并将用于显示补全结果的弹出框设置为 `treeView`。

    case 2:
        { // Country List
            completer->setModel(modelFromFile(":/resources/countries.txt"));
            QTreeView *treeView = new QTreeView;
            completer->setPopup(treeView);
            treeView->setRootIsDecorated(false);
            treeView->header()->hide();
            treeView->header()->setStretchLastSection(false);
            treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
            treeView->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
            contentsLabel->setText(tr("Enter name of your country"));
        }
        break;

下图展示了带有国家列表模型的 Completer。

展示国家列表模型内容的应用程序

如果case 的值为 3,则尝试进行单词补全。这通过一个包含从words.txt 中提取数据的QStringListModel 对象来实现。该模型已按case insensitively 进行排序。

下图展示了采用单词列表模型的“补全器”。

显示词表模型内容的应用程序

选择模型类型后,我们调用changeMode() 函数和changeCase() 函数,并相应地设置wrap选项。wrapCheckBox 的clicked()信号连接到completer 的setWrapAround()插槽。

    case 3:
        { // Word list
            completer->setModel(modelFromFile(":/resources/wordlist.txt"));
            completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
            contentsLabel->setText(tr("Enter a word"));
        }
        break;
    }

    changeMode(modeCombo->currentIndex());
    changeCase(caseCombo->currentIndex());
    completer->setWrapAround(wrapCheckBox->isChecked());
    lineEdit->setCompleter(completer);
    connect(wrapCheckBox, &QAbstractButton::clicked, completer, &QCompleter::setWrapAround);
}

changeMaxVisible() 用于更新补全器中可见项的最大数量。

void MainWindow::changeMaxVisible(int max)
{
    completer->setMaxVisibleItems(max);
}

about() 函数提供了关于该示例的简要说明。

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

main() 函数

main() 函数实例化QApplication 和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.