Completer 示例

Completer 示例展示了如何在模型提供的数据基础上,为输入小部件提供字符串自动完成功能。

本示例使用自定义项模型 FileSystemModel 和一个 QCompleter 对象。 QCompleter 是一个提供基于项模型自动完成的类。可以使用下拉菜单选择模型类型、完成模式和大小写敏感度。

资源文件

为了存储 countries.txtwords.txt,Completer 示例需要一个资源文件。资源文件包含以下代码

<!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,未做更改。

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 对象:modelCombmodeCombocaseCombo。默认情况下,modelComb 被设置为 QFileSystemModelmodeCombo 被设置为 "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 决定 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() 函数实例化 QAction 对象,以填充 fileMenuhelpMenu。将动作的 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.txtwords.txt。如果读取的是 words.txt 文件,我们返回一个具有 words 作为它的 QStringListcompleter 作为父级的 QStringListModel

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

如果读取的是 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,我们尝试完成单词。这使用包含从words.txt中提取的数据的QStringListModel完成,模型按不区分大小写地排序

下方的截图显示了具有单词列表模型的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()函数更新Completer中可见的最大项目数量。

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

© 2024 Qt公司有限公司。文档贡献的版权属于各自的拥有者。提供的文档受GNU自由文档许可版本1.3的条款约束,由自由软件基金会发布。Qt和相应的标志是芬兰和/或其他国家的Qt公司之商标。所有其他商标均为其各自所有者的财产。