Completer の例

Completer の例では、モデルによって提供されるデータに基づいて、入力ウィジェットに文字列補完機能を提供する方法を示します。

この例では、カスタム項目モデルFileSystemModelQCompleter オブジェクトを使用します。QCompleter は、項目モデルに基づいて補完機能を提供するクラスです。モデルのタイプ、補完モード、および大文字と小文字の区別は、コンボボックスを使って選択できます。

リソースファイル

Completer の例では、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;
};

このクラスは、QFileSystemModeldata() 関数がフォルダのみを返し、ドライブ・ラベルを返さないのとは異なり、data() が表示ロールのファイル・パス全体を返せるようにするためだけに作成されているため、コンストラクタとdata() 関数しか持っていません。これについてはFileSystemModel の実装で詳しく説明します。

FileSystemModel クラスの実装

FileSystemModel クラスのコンストラクタは、parentQFileSystemModel に渡すために使用されます。

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

QCompleter が一致を検索するために使用するQt::EditRole は変更されません。

MainWindow クラスの定義

MainWindow クラスはQMainWindow のサブクラスで、about()changeCase()changeMode()changeModel()changeMaxVisible() の 5 つのプライベート・スロットを実装しています。

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() の2つのプライベート関数があります。また、必要なプライベート・ウィジェットとして、3つのQComboBox オブジェクト、QCheckBoxQCompleterQLabelQLineEdit を宣言します。

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() 関数が呼び出されます。

modelCombmodeCombocaseCombo の3つのQComboBox オブジェクトを設定します。デフォルトでは、modelComboQFileSystemModel に、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 は、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);

次にマウスカーソルをQt::WaitCursor で上書きしてから、QStringList オブジェクトwordsfile の内容で埋めます。これが完了したら、マウスカーソルを復元します。

#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の2つのファイルが含まれている。読み込まれたfilewords.txt の場合、wordsQStringList とし、completer を親とするQStringListModel を返します。

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

filecountries.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() 関数は、ユーザーが選択したモデルに基づいて使用する項目モデルを変更する。

modelCombo のインデックスに基づいてアイテム・モデルを変更するために、switch 文が使用されます。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 でソートされます。

下のスクリーンショットは、単語リスト・モデルを使った 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 The Qt Company Ltd. 本書に含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 ここで提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。