补全器示例

Completer 示例展示了如何根据模型提供的数据为输入 widget 提供字符串补全功能。

该示例使用了一个自定义项目模型FileSystemModel 和一个QCompleter 对象。QCompleter 是一个基于项目模型提供补全功能的类。可以使用组合框选择模型类型、完成模式和大小写敏感度。

资源文件

Completter 示例需要一个资源文件来存储countries.txtwords.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() 能够为显示角色返回整个文件路径,而不像QFileSystemModeldata() 函数只返回文件夹而不返回驱动器标签。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 ,并初始化了私有成员。然后调用createMenu() 函数。

我们设置了三个QComboBox 对象:modelCombmodeCombocaseCombo 。默认情况下,modelCombo 设置为QFileSystemModelmodeCombo 设置为 "过滤弹出窗口",caseCombo 设置为 "大小写不敏感"。

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 并确定完成器中可见项的数量。

然后设置wrapCheckBoxcheckBox 决定completersetWrapAround() 属性是启用还是禁用。

    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() 函数用于实例化fileMenuhelpMenu 所需的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);

然后,在我们用file 的内容填充QStringList 对象words 之前,先用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.txtwords.txt。如果file 读取的是words.txt,我们将返回一个QStringListModelwords 作为它的QStringListcompleter 作为它的父对象。

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

如果file 读取的是countries.txt,那么我们需要一个包含words.count() 行、2 列和completer 作为父文件的QStandardItemModel

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

countries.txt中的标准行是

挪威 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,我们将尝试完成单词。这是通过QStringListModel 来完成的,其中包含从words.txt 中提取的数据。该模型的排序为case insensitively

下面的截图显示了使用单词列表模型的 Completer。

选择模型类型后,我们会调用changeMode() 函数和changeCase() 函数,并相应设置换行选项。wrapCheckBoxclicked() 信号连接到completersetWrapAround() 槽。

    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() 函数将QApplicationMainWindow 实例化,并调用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.