モデル/ビュー・プログラミング

モデル/ビュー・プログラミング入門

Qt には、モデル/ビューアーキテクチャを使用して、データとユーザーへの表示方法の関係を管理するアイテムビュークラスのセットがあります。このアーキテクチャによって導入された機能の分離により、開発者はアイテムの表示をより柔軟にカスタマイズできるようになり、また、既存のアイテムビューで幅広いデータソースを使用できるようにするための標準モデルインターフェースを提供します。このドキュメントでは、モデル/ビューパラダイムを簡単に紹介し、関連する概念の概要を説明し、アイテムビューシステムのアーキテクチャを説明します。アーキテクチャの各コンポーネントを説明し、提供されるクラスの使用方法を示す例を示します。

モデル/ビューアーキテクチャ

Model-View-Controller (MVC) はSmalltalkに由来するデザインパターンで、ユーザインタフェースを構築するときによく使われる。デザインパターンの中で、Gammaらはこう書いている:

MVCは3種類のオブジェクトから構成される。モデルはアプリケーション・オブジェクトであり、ビューはその画面表示であり、コントローラはユーザー・インターフェースがユーザーの入力に反応する方法を定義する。MVC以前は、ユーザーインターフェースのデザインはこれらのオブジェクトをひとまとめにする傾向があった。MVCでは、柔軟性と再利用性を高めるために、これらのオブジェクトを切り離します。

ビューオブジェクトとコントローラーオブジェクトを組み合わせると、モデル/ビューアーキテクチャになります。これでもデータの保存方法とユーザーへの表示方法は分離されていますが、同じ原則に基づいたよりシンプルなフレームワークが提供されます。この分離により、基礎となるデータ構造を変更することなく、同じデータを複数の異なるビューで表示したり、新しいタイプのビューを実装したりすることが可能になります。ユーザー入力を柔軟に扱えるようにするために、デリゲートという概念を導入した。このフレームワークでデリゲートを持つ利点は、データ項目のレンダリングと編集の方法をカスタマイズできることです。

モデル/ビュー・アーキテクチャ

モデルはデータソースと通信し、アーキテクチャ内の他のコンポーネントにインターフェースを提供する。通信の性質は、データソースの種類とモデルの実装方法に依存します。

ビューはモデルからモデル・インデックスを取得します。モデル・インデックスをモデルに提供することで、ビューはデータ・ソースからデータ項目を取得することができます。

標準的なビューでは、デリゲートがデータ項目をレンダリングします。項目が編集されると、デリゲートはモデル・インデックスを使ってモデルと直接通信します。

一般的に、モデル/ビュークラスは、モデル、ビュー、デリゲートの3つのグループに分けることができます。これらの各コンポーネントは、共通のインターフェースと、場合によっては機能のデフォルト実装を提供する抽象クラスによって定義されます。抽象クラスは、他のコンポーネントが期待する機能一式を提供するためにサブクラス化されることを意図しています。

モデル、ビュー、デリゲートは、シグナルとスロットを使って互いに通信します:

  • モデルからのシグナルは、データソースが保持するデータの変更をビューに知らせます。
  • ビューからのシグナルは、表示されているアイテムに対するユーザのインタラクションに関する情報を提供します。
  • デリゲートからのシグナルは、編集中にエディターの状態をモデルとビューに伝えるために使われます。

モデル

すべてのアイテムモデルはQAbstractItemModel クラスに基づいています。このクラスは、ビューとデリゲートがデータにアクセスするためのインターフェースを定義しています。データ自体はモデルに格納する必要はありません。別のクラスが提供するデータ構造やリポジトリ、ファイル、データベース、その他のアプリケーションコンポーネントに格納することができます。

モデルを取り巻く基本的な概念は、モデルクラスのセクションで説明します。

QAbstractItemModel は、テーブル、リスト、ツリーの形式でデータを表現するビューを扱うのに十分柔軟なデータへのインタフェースを提供します。しかし、リストやテーブルのようなデータ構造に対して新しいモデルを実装する場合、 と クラスがより良い出発点となります。なぜなら、これらは一般的な関数の適切なデフォルト実装を提供するからです。これらのクラスはそれぞれ、特殊な種類のリストやテーブルをサポートするモデルを提供するためにサブクラス化することができます。QAbstractListModel QAbstractTableModel

モデルをサブクラス化するプロセスについては、「新しいモデルの作成」のセクションで説明します。

Qtでは、データの項目を扱うために使用できる、いくつかの既成のモデルを提供しています:

  • QStringListModel は、 アイテムの単純なリストを保存するために使用されます。QString
  • QStandardItemModel 項目のより複雑なツリー構造を管理し、各項目は任意のデータを含むことができます。
  • QFileSystemModel ローカル・ファイリング・システムのファイルとディレクトリに関する情報を提供する。
  • QSqlQueryModel QSqlTableModel および は、モデル/ビューの規約を使用してデータベースにアクセスするために使用されます。QSqlRelationalTableModel

これらの標準モデルが要件を満たさない場合は、QAbstractItemModelQAbstractListModelQAbstractTableModel をサブクラス化して、独自のカスタム・モデルを作成することができます。

ビュー

QListView は項目のリストを表示し、QTableView はモデルからのデータをテーブルに表示し、QTreeView はモデル項目のデータを階層リストに表示します。これらのクラスはそれぞれ、QAbstractItemView 抽象ベース・クラスに基づいています。これらのクラスはすぐに使用できる実装ですが、カスタマイズされたビューを提供するためにサブクラス化することもできます。

利用可能なビューについては、ビュークラスのセクションで説明します。

デリゲート

QAbstractItemDelegate は、モデル/ビューフレームワークにおけるデリゲートの抽象基底クラスです。デフォルトのデリゲート実装は で提供されており、Qt の標準ビューではこれがデフォルトのデリゲートとして使用されます。しかし、 と は、ビューのアイテムをペイントしたり、エディタを提供するための独立した代替手段です。両者の違いは、 が現在のスタイルを使用してアイテムをペイントすることです。したがって、カスタムデリゲートを実装する場合や、Qtスタイルシートを使用する場合は、 を基本クラスとして使用することをお勧めします。QStyledItemDelegate QStyledItemDelegate QItemDelegate QStyledItemDelegate QStyledItemDelegate

デリゲートについては、デリゲート・クラスのセクションで説明します。

ソート

モデル/ビュー・アーキテクチャにおけるソートには2つの方法があります。

あなたのモデルがソート可能である場合、つまりQAbstractItemModel::sort() 関数を再実装している場合、QTableViewQTreeView の両方が、モデルデータをプログラムでソートできるAPIを提供します。さらに、QHeaderView::sortIndicatorChanged() シグナルをそれぞれQTableView::sortByColumn() スロットまたはQTreeView::sortByColumn() スロットに接続することで、インタラクティブなソート(ユーザーがビューのヘッダーをクリックしてデータをソートできるようにする)を有効にすることができます。

モデルが必要なインターフェイスを持っていない場合や、リストビューを使ってデータを表示したい場合の代替アプローチは、ビューでデータを表示する前に、プロキシモデルを使ってモデルの構造を変換することです。これについては、プロキシモデルのセクションで詳しく説明します。

便利なクラス

いくつかの便利なクラスは、Qt のアイテムベースのアイテムビューとテーブルクラスに依存するアプリケーションのために、標準のビュークラスから派生したものです。これらはサブクラス化されることを意図していません。

そのようなクラスの例としては、QListWidgetQTreeWidgetQTableWidget があります。

これらのクラスはビュークラスよりも柔軟性に欠け、任意のモデルで使用することはできません。項目ベースのクラス・セットを強く必要としない限り、項目ビューでのデータ処理にはモデル/ビュー・アプローチを使用することをお勧めします。

項目ベースのインターフェースを使用しながら、モデル/ビュー・アプローチによって提供される機能を利用したい場合は、QListViewQTableViewQTreeViewQStandardItemModel のようなビュー・クラスの使用を検討してください。

モデルとビューの使用法

以下のセクションでは、Qt でモデル/ビュー・パターンを使用する方法を説明します。各セクションには例が含まれており、その後に新しいコンポーネントの作成方法を示すセクションが続きます。

Qt に含まれる 2 つのモデル

Qt が提供する標準モデルの 2 つは、QStandardItemModelQFileSystemModel です。QStandardItemModel は多目的モデルで、リスト・ビュー、テーブル・ビュー、ツリー・ビューで必要とされる、さまざまな異なるデータ構造を表現するために使用することができます。QFileSystemModel は、ディレクトリの内容に関する情報を保持するモデルです。その結果、このモデル自身はデータの項目を保持せず、単にローカル・ファイリング・システム上のファイルとディレクトリを表現します。

QFileSystemModel は、すぐに実験に使えるモデルを提供し、既存のデータを使うように簡単に設定できる。このモデルを使用して、既製のビューを使用するためのモデルの設定方法を示し、モデルのインデックスを使用してデータを操作する方法を探ります。

既存のモデルでビューを使う

QFileSystemModel で使用するビューとしては、QListViewQTreeView のクラスが最適です。以下の例では、ディレクトリの内容をツリー・ビューで表示し、同じ情報をリスト・ビューで表示しています。ビューはユーザーの選択を共有するので、選択された項目は両方のビューで強調表示されます。

QFileSystemModel 、ディレクトリの内容を表示するビューを作成します。これは、モデルを使用する最も簡単な方法を示しています。モデルの構築と使用は、main() 関数の中で行われます:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QSplitter *splitter = new QSplitter;

    QFileSystemModel *model = new QFileSystemModel;
    model->setRootPath(QDir::currentPath());

モデルは、特定のファイルシステムのデータを使用するように設定されています。setRootPath ()の呼び出しは、ビューに公開するファイルシステム上のドライブをモデルに指示します。

モデルに保持されている項目を2つの異なる方法で検査できるように、2つのビューを作成します:

    QTreeView *tree = new QTreeView(splitter);
    tree->setModel(model);
    tree->setRootIndex(model->index(QDir::currentPath()));

    QListView *list = new QListView(splitter);
    list->setModel(model);
    list->setRootIndex(model->index(QDir::currentPath()));

ビューは、他のウィジェットと同じ方法で構築します。モデル内の項目を表示するためのビューの設定は、ディレクトリモデルを引数としてsetModel() 関数を呼び出すだけです。各ビューでsetRootIndex() 関数を呼び出し、カレントディレクトリのファイルシステムモデルから適切なモデルインデックスを渡すことで、モデルから供給されるデータをフィルタリングします。

この場合に使用されるindex() 関数は、QFileSystemModel に固有のものです。この関数にディレクトリを渡すと、モデルインデックスが返されます。モデル・インデックスについてはモデル・クラスで説明します。

残りの関数は、スプリッター・ウィジェット内にビューを表示し、アプリケーションのイベント・ループを実行するだけです:

    splitter->setWindowTitle("Two views onto the same file system model");
    splitter->show();
    return app.exec();
}

上記の例では、アイテムの選択をどのように扱うかについて言及しませんでした。上記の例では、アイテムの選択をどのように扱うかについて言及しませんでした。このテーマについては、アイテムビューでの選択の扱いについてのセクションで詳しく説明します。

モデルクラス

選択がどのように処理されるかを調べる前に、モデル/ビューフレームワークで使用される概念を調べると便利です。

基本概念

モデル/ビューアーキテクチャでは、モデルはビューとデリゲートがデータにアクセスするために使用する標準インターフェースを提供します。Qt では、標準インターフェースはQAbstractItemModel クラスによって定義されます。データの項目がどのような基礎的なデータ構造に格納されていても、QAbstractItemModel のサブクラスはすべて、項目のテーブルを含む階層構造としてデータを表現します。ビューは、モデル内のデータ項目にアクセスするためにこの規約を使用しますが、ユーザーにこの情報を表示する方法については制限されません。

モデルはまた、シグナルとスロットのメカニズムを通じて、データの変更について接続されたビューに通知します。

このセクションでは、モデルクラスを経由して他のコンポーネントがデータ項目にアクセスする方法の中心となる、いくつかの基本的な概念について説明します。より高度な概念については、後のセクションで説明します。

モデル・インデックス

データの表現とアクセス方法を確実に分離するために、モデル・インデックスの概念が導入されます。モデルを介して取得できる各情報は、モデル・インデックスによって表現されます。ビューとデリゲートは、これらのインデックスを使用して、表示するデータ項目を要求します。

その結果、モデルだけがデータを取得する方法を知る必要があり、モデルによって管理されるデータのタイプはかなり一般的に定義することができます。モデルインデックスには、それを作成したモデルへのポインタが含まれており、複数のモデルを扱う際の混乱を防ぐことができます。

QAbstractItemModel *model = index.model();

モデル・インデックスは、情報の一部への一時的な参照を提供し、モデルを介してデータを取得したり変更したりするために使用することができます。モデルは時々内部構造を再編成することがあるため、モデル・インデックスは無効になる可能性があり、保存すべきではありません。情報の一部への長期的な参照が必要な場合は、永続的なモデルインデックスを作成しなければなりません。これは、モデルが最新状態を保つ情報への参照を提供します。一時的なモデル・インデックスはQModelIndex クラスによって提供され、永続的なモデル・インデックスはQPersistentModelIndex クラスによって提供されます。

データ項目に対応するモデルインデックスを取得するには、行番号、列番号、親項目のモデルインデックスの3つのプロパティをモデルに指定する必要があります。以下のセクションでは、これらのプロパティの詳細について説明します。

行と列

最も基本的な形では、モデルは、項目が行番号と列番号によって配置される単純なテーブルとしてアクセスすることができます。行番号と列番号の使用は、コンポーネントが互いに通信できるようにするための慣習に過ぎません。行番号と列番号をモデルに指定することで、任意の項目に関する情報を取得することができ、その項目を表すインデックスを受け取ることができます:

QModelIndex index = model->index(row, column, ...);

リストやテーブルのような単純な単一レベルのデータ構造へのインターフェースを提供するモデルは、それ以外の情報を提供する必要はありませんが、上記のコードが示すように、モデルのインデックスを取得する際にはより多くの情報を提供する必要があります。

行と列

この図は、各項目が行番号と列番号のペアで配置される基本的なテーブルモデルの表現です。関連する行番号と列番号をモデルに渡すことで、データの項目を参照するモデルインデックスを取得します。

QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());

モデル内のトップレベル項目は、常に親項目としてQModelIndex() を指定することで参照されます。これについては次のセクションで説明します。

アイテムの親

モデルによって提供されるアイテムデータへのテーブルのようなインタフェースは、テーブルビューやリストビューでデータを使用する場合に理想的です。しかし、ツリービューのような構造では、モデルからアイテムに対してより柔軟なインタフェースを提供する必要があります。その結果、ツリービューの最上位アイテムが別のアイテムのリストを含むことができるのと同じように、各アイテムはアイテムの別のテーブルの親になることもできます。

モデル項目のインデックスを要求するとき、項目の親に関する情報を提供しなければなりません。モデルの外では、項目を参照する唯一の方法はモデルインデックスを介することなので、親モデルインデックスも与えなければなりません:

QModelIndex index = model->index(row, column, parent);
親、行、列

この図は、各項目が親、行番号、列番号によって参照されるツリーモデルの表現です。

項目「A」と「C」は、モデル内の最上位の兄弟として表されます:

QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());

項目 "A "はいくつかの子を持つ。項目 "B "のモデル・インデックスは、以下のコードで得られる:

QModelIndex indexB = model->index(1, 0, indexA);

項目の役割

モデル内のアイテムは、他のコンポーネントのために様々な役割を果たすことができます。例えば、Qt::DisplayRole は、ビューでテキストとして表示される文字列にアクセスするために使われます。通常、アイテムにはさまざまな役割のデータが含まれ、標準的な役割はQt::ItemDataRole で定義されています。

アイテムに対応するモデルインデックスを渡し、ロールを指定して必要なデータ型を取得することで、モデルにアイテムのデータを要求することができます:

QVariant value = model->data(index, role);
項目のロール

ロールは、参照されるデータのタイプをモデルに示します。ビューはロールをさまざまな方法で表示することができるので、それぞれのロールに適切な情報を与えることが重要です。

新しいモデルの作成」セクションで、ロールの具体的な使用法について詳しく説明します。

項目データの一般的な用途のほとんどは、Qt::ItemDataRole で定義されている標準ロールでカバーされています。各ロールに適切な項目データを提供することで、モデルはビューやデリゲートに対して、項目がユーザにどのように表示されるべきかのヒントを提供することができます。異なる種類のビューは、必要に応じてこの情報を解釈したり無視したりする自由があります。また、アプリケーション固有の目的のために追加の役割を定義することも可能です。

概要

  • モデルインデックスは、ビューとデリゲートに、モデルによって提供されるアイテムの位置に関する情報を、基礎となるデータ構造に依存しない方法で与えます。
  • アイテムは、行番号と列番号、そして親アイテムのモデルインデックスによって参照されます。
  • モデルインデックスは、ビューやデリゲートなど、他のコンポーネントの要求に応じてモデルによって構築されます。
  • index() を使用してインデックスが要求されたときに、親アイテムに対して有効なモデルインデックスが指定されている場合、返されるインデックスは、モデル内のその親アイテムの下のアイテムを参照します。取得されたインデックスは、その項目の子を参照します。
  • index() を使用してインデックスが要求されたときに、親項目に無効なモデル・インデックスが指定された場合、返されるインデックスはモデル内の最上位項目を参照します。
  • role は、項目に関連付けられているさまざまな種類のデータを区別します。

モデルインデックスの使用

モデルインデックスを使用してモデルからデータを取得する方法を示すために、ビューなしでQFileSystemModel をセットアップし、ファイルとディレクトリの名前をウィジェットに表示します。これはモデルの通常の使い方を示しているわけではありませんが、モデルインデックスを扱うときにモデルで使われる規約を示しています。

QFileSystemModel 読み込みは、システムリソースの使用を最小限にするために非同期です。このモデルを扱う際には、そのことを考慮しなければなりません。

ファイル・システム・モデルを次のように構築します:

    auto *model = new QFileSystemModel;

    auto onDirectoryLoaded = [model, layout, &window](const QString &directory) {
        QModelIndex parentIndex = model->index(directory);
        const int numRows = model->rowCount(parentIndex);
        for (int row = 0; row < numRows; ++row) {
            QModelIndex index = model->index(row, 0, parentIndex);

            QString text = model->data(index, Qt::DisplayRole).toString();
            // Display the text in a widget.
            auto *label = new QLabel(text, &window);
            layout->addWidget(label);
        }
    };

    QObject::connect(model, &QFileSystemModel::directoryLoaded, onDirectoryLoaded);
    model->setRootPath(QDir::currentPath());

この場合、まずデフォルトのQFileSystemModel を設定する。そのシグナルdirectoryLoaded(QString) をラムダに接続し、そのモデルが提供するindex() の特定の実装を使用して、ディレクトリの親インデックスを取得します。

ラムダでは、rowCount ()関数を使ってモデルの行数を決定する。

簡単のために、我々はモデルの最初の列の項目だけに興味があります。各行を順番に調べ、各行の最初の項目のモデルインデックスを取得し、モデル内のその項目に対して格納されているデータを読み取ります。

        for (int row = 0; row < numRows; ++row) {
            QModelIndex index = model->index(row, 0, parentIndex);

モデルインデックスを取得するには、行番号、列番号(最初の列はゼロ)、そして、欲しいすべての項目の親の適切なモデルインデックスを指定します。各項目に格納されているテキストは、モデルのdata() 関数を使って取得します。モデル・インデックスとDisplayRole を指定して、項目のデータを文字列の形で取得します。

            QString text = model->data(index, Qt::DisplayRole).toString();

        }

最後に、QFileSystemModel のルートパスを設定して、データの読み込みを開始し、ラムダをトリガーします。

上記の例は、モデルからデータを取得するための基本原則を示しています:

  • モデルの次元は、rowCount ()とcolumnCount ()を使って見つけることができます。これらの関数は通常、親モデルのインデックスを指定する必要があります。
  • モデル・インデックスは、モデル内の項目にアクセスするために使用されます。項目を指定するには、行、列、および親モデル・インデックスが必要です。
  • モデル内の最上位の項目にアクセスするには、親インデックスとしてQModelIndex() で NULL モデルインデックスを指定します。
  • 項目には、さまざまなロールのデータが含まれます。特定のロールのデータを取得するには、モデル・インデックスとロールの両方をモデルに与えなければなりません。

さらに読む

QAbstractItemModel が提供する標準インタフェースを実装することで、新しいモデルを作成することができます。新しいモデルの作成」セクションでは、文字列のリストを保持する便利なすぐに使えるモデルを作成することで、この方法を示します。

ビュークラス

概念

モデル/ビューアーキテクチャでは、ビューはモデルからデータ項目を取得し、ユーザーに提示します。データが表示される方法は、モデルによって提供されるデータの表現に似ている必要はありません。

コンテンツとプレゼンテーションの分離は、QAbstractItemModel によって提供される標準モデルインターフェース、QAbstractItemView によって提供される標準ビューインターフェース、および一般的な方法でデータ項目を表すモデルインデックスの使用によって達成されます。ビューは通常、モデルから得られたデータの全体的なレイアウトを管理します。ビューは、データの個々の項目を自分でレンダリングしたり、デリゲートを使用してレンダリングと編集の両方の機能を処理したりします。

データを表示するだけでなく、ビューは項目間のナビゲーションや項目選択の一部も処理します。ビューはまた、コンテキストメニューやドラッグ&ドロップなどの基本的なユーザーインターフェース機能も実装する。ビューは、アイテムのデフォルトの編集機能を提供することもできますし、デリゲートと連携してカスタムエディタを提供することもできます。

ビューはモデルなしでも構築できますが、有用な情報を表示する前にモデルを提供する必要があります。ビューは、ユーザーが選択した項目を追跡します。選択項目は、ビューごとに別々に管理することも、複数のビュー間で共有することもできます。

QTableViewQTreeView のように、項目だけでなくヘッダも表示するビューもあります。これらもビュークラスQHeaderView によって実装されています。ヘッダーは通常、それを含むビューと同じモデルにアクセスします。これらはQAbstractItemModel::headerData() 関数を使用してモデルからデータを取得し、通常はラベルの形式でヘッダー情報を表示します。新しいヘッダをQHeaderView クラスからサブクラス化することで、ビューにより特化したラベルを提供することができます。

既存のビューの使用

QListView は、モデルからの項目を単純なリストとして、または古典的なアイコンビューの形で表示することができます。QTreeView は、モデルからの項目をリストの階層として表示し、深くネストされた構造をコンパクトに表現することができます。QTableView は、モデルからの項目を表の形で表示し、表計算アプリケーションのレイアウトによく似ています。

上に示した標準ビューのデフォルトの動作は、ほとんどのアプリケーションにとって十分なものです。これらは基本的な編集機能を提供し、より特殊なユーザーインターフェースのニーズに合わせてカスタマイズすることができます。

モデルの使用

モデル例として作成した文字列リストモデルを用いて、いくつかのデータをセットアップし、モデルの内容を表示するビューを構築します。これはすべて一つの関数の中で行うことができます:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

// Unindented for quoting purposes:
QStringList numbers;
numbers << "One" << "Two" << "Three" << "Four" << "Five";

QAbstractItemModel *model = new StringListModel(numbers);

StringListModelQAbstractItemModel として宣言されていることに注意してください。 これにより、モデルに対する抽象インターフェースを使用することができ、文字列リストモデルを別のモデルに置き換えたとしても、コードが動作することが保証されます。

文字列リストモデルの項目を表示するには、QListView が提供するリストビューで十分です。このビューを作成し、以下のコードでモデルをセットアップします:

QListView *view = new QListView;
view->setModel(model);

ビューは通常の方法で表示されます:

    view->show();
    return app.exec();
}

ビューはモデルの内容をレンダリングし、モデルのインターフェースを介してデータにアクセスします。ユーザが項目を編集しようとすると、ビューはデフォルトのデリゲートを使ってエディタウィジェットを提供します。

上の図は、QListView が文字列リストモデルのデータをどのように表しているかを示しています。このモデルは編集可能なので、ビューは自動的にデフォルトのデリゲートを使ってリストの各項目を編集できるようにします。

モデルの複数のビューを使う

同じモデルに対して複数のビューを提供することは、単に各ビューに同じモデルを設定するだけのことです。以下のコードでは、2つのテーブルビューを作成し、それぞれこの例のために作成した同じ単純なテーブルモデルを使用しています:

    QTableView *firstTableView = new QTableView;
    QTableView *secondTableView = new QTableView;

    firstTableView->setModel(model);
    secondTableView->setModel(model);

モデル/ビューアーキテクチャにおけるシグナルとスロットの使用は、モデルへの変更が接続されたすべてのビューに伝搬されることを意味し、使用されるビューに関係なく常に同じデータにアクセスできることを保証します。

上の図は、同じモデルに対する2つの異なるビューを示しています。モデルからのデータはビュー間で一貫して表示されますが、各ビューは独自の内部選択モデルを保持しています。これは特定の状況では便利ですが、多くのアプリケーションでは、共有された選択モデルが望まれます。

アイテムの選択を扱う

ビュー内でアイテムの選択を処理するメカニズムは、QItemSelectionModel クラスによって提供されます。すべての標準ビューは、デフォルトで独自の選択モデルを構築し、通常の方法でそれらと対話します。ビューで使用されている選択モデルはselectionModel() 関数で取得することができ、setSelectionModel() で代替の選択モデルを指定することができます。ビューが使用する選択モデルを制御する機能は、同じモデルデータに対して複数の一貫したビューを提供したい場合に便利です。

一般的に、モデルやビューをサブクラス化しない限り、選択項目の内容を直接操作する必要はありません。しかし、必要であれば、選択モデルへのインターフェースにアクセスすることができます。

ビュー間での選択項目の共有

ビュークラスがデフォルトで独自の選択モデルを提供するのは便利ですが、同じモデルに対して複数のビューを使用する場合、モデルのデータとユーザーの選択の両方がすべてのビューで一貫して表示されることが望ましいことがよくあります。ビュークラスは内部の選択モデルを置き換えることができるので、以下の行でビュー間の統一された選択を実現することができます:

    secondTableView->setSelectionModel(firstTableView->selectionModel());

つ目のビューには、1つ目のビューの選択モデルが与えられます。これで両方のビューが同じ選択モデルで操作されるようになり、データと選択された項目の同期が保たれます。

上記の例では、同じモデルのデータを表示するために、同じタイプの2つのビューが使用されました。例えば、テーブルビューでは連続した選択項目が、ツリービューではハイライトされた項目の断片として表現されることがあります。

デリゲートクラス

概念

Model-View-Controllerパターンとは異なり、モデル/ビューの設計では、ユーザーとのインタラクションを管理するための完全に独立したコンポーネントを含みません。一般的に、ビューはユーザーに対するモデルデータの表示と、ユーザー入力の処理を担当します。この入力を取得する方法にある程度の柔軟性を持たせるために、相互作用はデリゲートによって実行されます。これらのコンポーネントは入力機能を提供し、ビューによっては個々のアイテムのレンダリングも担当します。デリゲートを制御するための標準インターフェースはQAbstractItemDelegate クラスで定義されています。

デリゲートは、paint() とsizeHint() 関数を実装することで、自分自身でその内容をレンダリングできることが期待されています。しかし、単純なウィジェットベースのデリゲートは、QAbstractItemDelegate の代わりにQStyledItemDelegate をサブクラス化し、これらの関数のデフォルト実装を利用することができます。

デリゲートのエディタは、ウィジェットを使用して編集処理を管理するか、イベントを直接処理することで実装できます。最初のアプローチについては、このセクションで後述します。

既存のデリゲートを使用する

Qt で提供されている標準のビューでは、QStyledItemDelegate のインスタンスを使用して編集機能を提供しています。このデリゲート・インターフェースのデフォルト実装は、各標準ビューの通常のスタイルで項目をレンダリングします:QListView QTableViewQTreeView

すべての標準的な役割は、標準的なビューで使用されるデフォルトのデリゲートによって処理されます。これらの解釈方法は、QStyledItemDelegate のドキュメントに記述されています。

ビューで使用されるデリゲートはitemDelegate() 関数で返されます。setItemDelegate() 関数を使用すると、標準ビューにカスタムのデリゲートをインストールすることができます。カスタムビューにデリゲートを設定する場合は、この関数を使用する必要があります。

単純なデリゲート

ここで実装されているデリゲートは、QSpinBox を使用して編集機能を提供し、主に整数を表示するモデルでの使用を想定しています。この目的のために整数ベースのカスタムテーブルモデルを設定しましたが、カスタムデリゲートはデータ入力を制御するので、QStandardItemModel を代わりに使用することも簡単にできました。モデルの内容を表示するためにテーブルビューを作成し、編集のためにカスタムデリゲートを使用します。

カスタム表示関数を書きたくないので、QStyledItemDelegate からデリゲートをサブクラス化します。しかし、エディターウィジェットを管理する関数は提供しなければなりません:

class SpinBoxDelegate : public QStyledItemDelegate
{
    Q_OBJECT

public:
    SpinBoxDelegate(QObject *parent = nullptr);

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const override;

    void setEditorData(QWidget *editor, const QModelIndex &index) const override;
    void setModelData(QWidget *editor, QAbstractItemModel *model,
                      const QModelIndex &index) const override;

    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
                              const QModelIndex &index) const override;
};

SpinBoxDelegate::SpinBoxDelegate(QObject *parent)
    : QStyledItemDelegate(parent)
{
}

デリゲートが構築されるときには、エディター・ウィジェットはセットアップされないことに注意してください。エディター・ウィジェットは、必要なときだけ作成します。

エディタの提供

この例では、テーブルビューがエディタを提供する必要があるとき、デリゲートに、変更される項目に適したエディタウィジェットを提供するように依頼します。createEditor() 関数には、デリゲートが適切なウィジェットを設定できるようにするために必要なものがすべて用意されています:

QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
                                       const QStyleOptionViewItem &/* option */,
                                       const QModelIndex &/* index */) const
{
    QSpinBox *editor = new QSpinBox(parent);
    editor->setFrame(false);
    editor->setMinimum(0);
    editor->setMaximum(100);

    return editor;
}

エディタウィジェットが不要になったら、ビューが責任をもって破棄するので、エディタウィジェットへのポインタを保持する必要はないことに注意してください。

ユーザーが期待する標準的な編集ショートカットを提供するために、デリゲートのデフォルトイベントフィルターをエディターにインストールします。より洗練された動作を可能にするために、追加のショートカットをエディタに追加することができます。これらについては、編集ヒントのセクションで説明します。

ビューは、エディターのデータとジオメトリが正しく設定されるように、後で定義する関数を呼び出します。ビューから提供されるモデルインデックスに応じて、異なるエディタを作成することができます。例えば、整数の列と文字列の列がある場合、どちらの列が編集されているかに応じて、QSpinBox またはQLineEdit を返すことができます。

デリゲートはモデルデータをエディタにコピーする関数を提供しなければなりません。この例では、display role に格納されているデータを読み取り、それに応じてスピンボックスの値を設定します。

void SpinBoxDelegate::setEditorData(QWidget *editor,
                                    const QModelIndex &index) const
{
    int value = index.data(Qt::EditRole).toInt();

    QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
    spinBox->setValue(value);
}

この例では、エディタウィジェットがスピンボックスであることを知っていますが、モデル内の異なるデータ型に対して異なるエディタを提供することもできます。この場合、メンバ関数にアクセスする前に、ウィジェットを適切な型にキャストする必要があります。

モデルへのデータ送信

ユーザがスピンボックスの値の編集を終えると、ビューはsetModelData() 関数を呼び出して、編集した値をモデルに保存するようデリゲートに依頼します。

void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                                   const QModelIndex &index) const
{
    QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
    spinBox->interpretText();
    int value = spinBox->value();

    model->setData(index, value, Qt::EditRole);
}

ビューはデリゲートのためにエディタウィジェットを管理するので、提供されたエディタの内容でモデルを更新するだけでよいのです。この場合、スピンボックスが最新であることを確認し、指定されたインデックスを使用して、それが含む値でモデルを更新します。

標準のQStyledItemDelegate クラスは、編集が終了するとcloseEditor() シグナルを発行してビューに通知します。ビューは、エディタウィジェットが閉じられ、破棄されることを保証します。この例では、簡単な編集機能を提供するだけなので、このシグナルを発する必要はありません。

データに対するすべての操作は、QAbstractItemModel によって提供されるインターフェイスを通して実行されます。このため、デリゲートは操作するデータのタイプからほとんど独立していますが、特定のタイプのエディター・ウィジェットを使用するためには、いくつかの仮定を行う必要があります。この例では、モデルは常に整数値を含むと仮定していますが、QVariant は予期しないデータに対して賢明なデフォルト値を提供するので、異なる種類のモデルでもこのデリゲートを使用することができます。

エディタのジオメトリの更新

エディタのジオメトリを管理するのはデリゲートの責任です。ジオメトリは、エディタが作成されたとき、およびビュー内のアイテムのサイズや位置が変更されたときに設定されなければなりません。幸いなことに、ビューはview option オブジェクトの中に必要なジオメトリ情報をすべて提供します。

void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
                                           const QStyleOptionViewItem &option,
                                           const QModelIndex &/* index */) const
{
    editor->setGeometry(option.rect);
}

この場合、アイテムの矩形のビューオプションで提供されるジオメトリ情報を使用するだけです。複数の要素を持つアイテムをレンダリングするデリゲートは、アイテムの矩形を直接使用しません。それは、アイテム内の他のエレメントに関連してエディタを配置することになります。

編集のヒント

編集後、デリゲートは他のコンポーネントに編集処理の結果に関するヒントを提供し、その後の編集操作を助けるヒントを提供する必要があります。これは、適切なヒントとともにcloseEditor() シグナルを発することで実現されます。これは、スピンボックスの構築時にインストールしたデフォルトのQStyledItemDelegate イベントフィルターによって処理されます。

スピンボックスの振る舞いは、よりユーザーフレンドリーになるように調整することができます。QStyledItemDelegate によって提供されるデフォルトのイベントフィルターでは、ユーザーがスピンボックスの選択を確定するためにReturn を押すと、デリゲートは値をモデルにコミットし、スピンボックスを閉じます。スピンボックスに独自のイベントフィルタをインストールし、ニーズに合った編集ヒントを提供することで、この動作を変更することができます。例えば、ビュー内の次のアイテムの編集を自動的に開始するために、EditNextItem ヒントを使用してcloseEditor() を発行することができます。

イベントフィルタを使用する必要がないもう一つのアプローチは、独自のエディタウィジェットを提供することです。おそらく、利便性のためにQSpinBox をサブクラス化します。この代替アプローチでは、エディターウィジェットの振る舞いをよりコントロールすることができます。標準のQtエディタ・ウィジェットの動作をカスタマイズする必要がある場合、デリゲートにイベント・フィルタをインストールする方が簡単です。

デリゲートはこれらのヒントを出す必要はありませんが、出さないものはアプリケーションへの統合性が低く、一般的な編集アクションをサポートするヒントを出すものよりも使い勝手が悪くなります。

アイテムビューでの選択の処理

概念

項目ビュークラスで使用される選択モデルは、モデル/ビューアーキテクチャの機能に基づいた選択の一般的な説明を提供します。選択項目を操作するための標準クラスは、提供されているアイテムビューには十分ですが、選択モデルによって、独自のアイテムモデルやビューの要件に合わせて、特殊な選択モデルを作成することができます。

ビューで選択された項目に関する情報は、QItemSelectionModel クラスのインスタンスに格納されます。これは、単一のモデル内のアイテムのモデルインデックスを保持し、どのビューからも独立しています。1つのモデル上に多くのビューが存在することがあるため、ビュー間で選択項目を共有することが可能であり、アプリケーションは一貫した方法で複数のビューを表示することができます。

選択範囲は、選択範囲によって構成されます。これらは、選択されたアイテムの各範囲の開始と終了のモデルインデックスのみを記録することによって、アイテムの大規模な選択に関する情報を効率的に保持します。非連続的な選択項目は、複数の選択範囲を使って選択項目を記述することで構成されます。

選択は、選択モデルによって保持されたモデルインデックスのコレクションに適用されます。最近適用された項目の選択は、現在の選択として知られています。この選択の効果は、ある種の選択コマンドを使うことによって、適用された後でも変更することができます。これらについてはこのセクションで後述する。

現在の項目と選択された項目

ビューには、常にカレント項目と選択項目があります。ある項目が現在の項目であると同時に選択されていることもあります。例えば、キーボードナビゲーションでは、カレントアイテムが必要です。

以下の表は、カレントアイテムとセレクテッドアイテムの違いを示しています。

現在の項目選択された項目
カレントアイテムは1つだけです。選択項目は複数存在できる。
カレント項目は、キー操作やマウスボタンのクリックによって変更される。アイテムの選択状態は、あらかじめ定義されたいくつかのモード(単一選択、複数選択など)に応じて、設定または解除される。- ユーザがアイテムを操作すると、現在のアイテムが編集されます。
編集キー(F2 )が押されるか、アイテムがダブルクリックされると、現在のアイテムが編集されます(編集が有効になっている場合)。現在の項目は、アンカーと一緒に使用して、選択または選択解除されるべき範囲を指定することができます(または、その2つの組み合わせ)。
現在の項目はフォーカス矩形で示されます。選択された項目は選択矩形で示されます。

選択を操作する場合、QItemSelectionModel をアイテムモデル内の全アイテムの選択状態の記録と考えると便利なことが多い。一旦選択モデルがセットアップされると、どの項目がすでに選択されているかを知る必要なしに、項目のコレクションを選択したり、選択解除したり、選択状態を切り替えたりすることができる。すべての選択されたアイテムのインデックスはいつでも取り出すことができ、シグナルとスロットのメカニズムによって、他のコンポーネントに選択モデルの変更を知らせることができる。

選択モデルの使用

標準のビュークラスは、ほとんどのアプリケーションで使えるデフォルトの選択モデルを提供します。あるビューに属する選択モデルは、そのビューのselectionModel() 関数を使用して取得することができます。また、setSelectionModel() を使用して多くのビュー間で共有することができます。そのため、通常、新しい選択モデルを作成する必要はありません。

選択項目は、モデルと、QItemSelection に対するモデルのインデックスのペアを指定することで作成されます。このインデックスは、与えられたモデル内の項目を参照するために使用され、選択された項目のブロック内の左上と右下の項目として解釈されます。モデル内のアイテムに選択を適用するには、選択モデルに選択アイテムを投入する必要があります。これはいくつかの方法で実現でき、それぞれが選択モデルにすでに存在する選択アイテムに異なる影響を与えます。

項目の選択

選択項目の主な機能のいくつかを示すために、合計32の項目を持つカスタムテーブルモデルのインスタンスを作成し、そのデータに対してテーブルビューを開きます:

    TableModel *model = new TableModel(8, 4, &app);

    QTableView *table = new QTableView(0);
    table->setModel(model);

    QItemSelectionModel *selectionModel = table->selectionModel();

テーブルビューのデフォルトの選択モデルは、後で使用するために取得されます。テーブルビューのデフォルトの選択モデルは後で使用するために取得されます。モデル内の項目は変更せず、ビューがテーブルの左上に表示するいくつかの項目を選択します。そのためには、選択される領域の左上と右下の項目に対応するモデルインデックスを取得する必要があります:

    QModelIndex topLeft;
    QModelIndex bottomRight;

    topLeft = model->index(0, 0, QModelIndex());
    bottomRight = model->index(5, 2, QModelIndex());

モデルでこれらの項目を選択し、テーブルビューでそれに対応する変化を見るためには、選択オブジェクトを作成し、それを選択モデルに適用する必要があります:

    QItemSelection selection(topLeft, bottomRight);
    selectionModel->select(selection, QItemSelectionModel::Select);

selection flags 。この場合、フラグによって、選択オブジェクトに記録された項目は、以前の状態にかかわらず選択モデルに含まれます。その結果、選択項目がビューに表示されます。

項目の選択は、選択フラグによって定義されたさまざまな操作によって変更することができます。これらの操作の結果得られる選択は複雑な構造を持つかもしれませんが、選択モデルによって効率的に表現されます。さまざまな選択フラグを使って選択項目を操作する方法については、選択項目の更新方法を説明するときに説明します。

選択状態の読み込み

選択モデルに格納されたモデルのインデックスは、selectedIndexes() 関数を使って読み込むことができます。この関数はモデルインデックスのソートされていないリストを返すので、それがどのモデルのものであるかがわかっている限り、それを反復処理することができます:

    const QModelIndexList indexes = selectionModel->selectedIndexes();

    for (const QModelIndex &index : indexes) {
        QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
        model->setData(index, text);
    }

上記のコードでは、範囲ベースのforループを使って、選択モデルから返されたインデックスに対応する項目を繰り返し処理し、修正している。

選択モデルは、選択の変更を示すシグナルを発する。これらのシグナルは、選択項目全体とアイテムモデルで現在フォーカスされているアイテムの両方の変更について、他のコンポーネントに通知します。selectionChanged() シグナルをスロットに接続し、セレクションが変更されたときに選択または選択解除されたモデル内のアイテムを調べることができます。QItemSelection 1つは新しく選択されたアイテムに対応するインデックスのリストを含み、もう1つは新しく選択解除されたアイテムに対応するインデックスを含んでいます。

次のコードでは、selectionChanged ()シグナルを受け取り、選択された項目を文字列で埋め、選択解除された項目の内容をクリアするスロットを用意しています。

void MainWindow::updateSelection(const QItemSelection &selected,
    const QItemSelection &deselected)
{
    QModelIndexList items = selected.indexes();

    for (const QModelIndex &index : std::as_const(items)) {
        QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
        model->setData(index, text);
    }

    items = deselected.indexes();

    for (const QModelIndex &index : std::as_const(items)) {
        model->setData(index, QString());
}

currentChanged() シグナルを、2つのモデル・インデックスで呼び出されるスロットに接続することで、現在フォーカスされているアイテムを追跡することができます。これらは、以前にフォーカスされた項目と、現在フォーカスされている項目に対応しています。

次のコードでは、currentChanged() シグナルを受信するスロットを提供し、提供された情報を使用して、QMainWindow のステータスバーを更新します:

void MainWindow::changeCurrent(const QModelIndex &current,
    const QModelIndex &previous)
{
    statusBar()->showMessage(
        tr("Moved from (%1,%2) to (%3,%4)")
            .arg(previous.row()).arg(previous.column())
            .arg(current.row()).arg(current.column()));
}

これらのシグナルを使えば、ユーザーによる選択を監視することは簡単ですが、選択モデルを直接更新することもできます。

選択の更新

選択コマンドは、QItemSelectionModel::SelectionFlag で定義された選択フラグの組み合わせによって提供されます。各選択フラグは、select() 関数のいずれかが呼ばれたときに、選択モデルの内部でどのように選択項目の記録を更新するかを指示します。最もよく使われるフラグはSelect フラグで、指定された項目を選択されたものとして記録するように選択モデルに指示します。Toggle フラグは、選択モデルに指定された項目の状態を反転させ、指定された非選択項目を選択し、現在選択されている項目を非選択にします。Deselect フラグは、指定されたすべての項目の選択を解除します。

選択モデルの個々の項目は、選択項目を作成し、それを選択モデルに適用することで更新されます。次のコードでは、Toggle コマンドを使用して、指定された項目の選択状態を反転させながら、上記のテーブルモデルに2つ目の項目の選択状態を適用しています。

    QItemSelection toggleSelection;

    topLeft = model->index(2, 1, QModelIndex());
    bottomRight = model->index(7, 3, QModelIndex());
    toggleSelection.select(topLeft, bottomRight);

    selectionModel->select(toggleSelection, QItemSelectionModel::Toggle);

この操作の結果はテーブルビューに表示され、何を達成したかを視覚化する便利な方法を提供します:

デフォルトでは、選択コマンドはモデルインデックスで指定された個々の項目に対してのみ動作します。しかし、選択コマンドを記述するために使用されるフラグは、行や列全体を変更するために追加のフラグと組み合わせることができます。例えば、SelectRows を組み合わせたコマンドでselect() を呼び出した場合、参照された項目を含む行全体が選択されます。以下のコードは、RowsColumns フラグの使用を示している:

    QItemSelection columnSelection;

    topLeft = model->index(0, 1, QModelIndex());
    bottomRight = model->index(0, 2, QModelIndex());

    columnSelection.select(topLeft, bottomRight);

    selectionModel->select(columnSelection,
        QItemSelectionModel::Select | QItemSelectionModel::Columns);

    QItemSelection rowSelection;

    topLeft = model->index(0, 0, QModelIndex());
    bottomRight = model->index(1, 0, QModelIndex());

    rowSelection.select(topLeft, bottomRight);

    selectionModel->select(rowSelection,
        QItemSelectionModel::Select | QItemSelectionModel::Rows);

選択モデルには4つのインデックスしか与えられませんが、ColumnsRows の選択フラグを使うと、2つの列と2つの行が選択されることになります。次の画像は、これら2つの選択結果を示しています:

この例のモデルで実行されたコマンドは、すべてモデル内のアイテムの選択を蓄積するものでした。選択をクリアしたり、現在の選択を新しいものに置き換えたりすることもできます。

現在の選択項目を新しい選択項目に置き換えるには、他の選択フラグとCurrent フラグを組み合わせます。このフラグを使ったコマンドは、選択モデルに対して、現在のモデルインデックスのコレクションをselect() の呼び出しで指定されたものに置き換えるように指示します。新しいセレクションを追加する前にすべてのセレクションをクリアするには、他のセレクションフラグとClear フラグを組み合わせてください。これは、選択モデルのモデルインデックスのコレクションをリセットする効果があります。

モデル内のすべての項目の選択

モデル内のすべての項目を選択するには、モデルの各レベルに対して、そのレベルのすべての項目を網羅する選択項目を作成する必要があります。これは、与えられた親インデックスを持つ左上と右下の項目に対応するインデックスを検索することで行います:

    QModelIndex topLeft = model->index(0, 0, parent);
    QModelIndex bottomRight = model->index(model->rowCount(parent)-1,
        model->columnCount(parent)-1, parent);

これらのインデックスとモデルで選択が構築されます。そして、対応する項目が選択モデルで選択されます:

    QItemSelection selection(topLeft, bottomRight);
    selectionModel->select(selection, QItemSelectionModel::Select);

これはモデル内のすべてのレベルで実行される必要がある。最上位の項目については、通常の方法で親インデックスを定義します:

階層モデルの場合、hasChildren ()関数を使用して、任意のアイテムが他のレベルのアイテムの親であるかどうかを判断します。

新しいモデルの作成

モデル/ビューコンポーネント間の機能の分離により、既存のビューを利用できるモデルを作成することができます。このアプローチにより、QListViewQTableViewQTreeView などの標準的なグラフィカル・ユーザー・インターフェース・コンポーネントを使用して、さまざまなソースからのデータを表示することができます。

QAbstractItemModel クラスは、情報を階層構造に配置するデータ・ソースをサポートするのに十分な柔軟性を持つインターフェイスを提供し、データが何らかの方法で挿入、削除、修正、ソートされる可能性を可能にします。また、ドラッグ・アンド・ドロップ操作もサポートしています。

QAbstractListModelQAbstractTableModel クラスは、より単純な非階層型データ構造へのインタフェースをサポートしており、単純なリストモデルやテーブルモデルの出発点として使いやすくなっています。

このセクションでは、モデル/ビュー・アーキテクチャの基本原則を探るために、単純な読み取り専用モデルを作成します。このセクションの後半では、この単純なモデルをユーザーが項目を変更できるようにします。

より複雑なモデルの例については、単純なツリーモデルの例を参照してください。

QAbstractItemModel サブクラスの要件については、「Model Subclassing Reference」ドキュメントで詳しく説明しています。

モデルの設計

既存のデータ構造に対して新しいモデルを作成する場合、データへのインタフェースを提供するためにどのタイプのモデルを使うべきかを検討することが重要です。データ構造が項目のリストやテーブルとして表現できる場合、QAbstractListModelQAbstractTableModel をサブクラス化することができます。

しかし、基礎となるデータ構造が階層ツリー構造でしか表現できない場合は、QAbstractItemModel をサブクラス化する必要があります。このアプローチは、単純なツリー・モデルの例で取られています。

このセクションでは、文字列のリストに基づいた単純なモデルを実装します。そのため、QAbstractListModel は、それを構築するための理想的な基本クラスを提供します。

基礎となるデータ構造がどのような形式をとるにせよ、通常、特化されたモデルでは、基礎となるデータ構造により自然にアクセスできるように、標準のQAbstractItemModel APIを補足するのが良い考えです。これにより、モデルへのデータ投入がより簡単になり、なおかつ他の一般的なモデル/ビューコンポーネントが標準APIを使ってモデルと対話できるようになります。以下に説明するモデルは、まさにこの目的のためにカスタムコンストラクタを提供します。

読み取り専用のサンプルモデル

ここで実装するモデルは、標準のQStringListModel クラスをベースにした、シンプルで階層化されていない読み取り専用のデータモデルです。内部データソースとしてQStringList を持ち、機能するモデルを作るために必要なものだけを実装しています。実装を簡単にするために、QAbstractListModel をサブクラスにしています。これは、リストモデルのための賢明なデフォルトの振る舞いを定義しており、QAbstractItemModel クラスよりもシンプルなインタフェースを公開しているからです。

モデルを実装する際に重要なことは、QAbstractItemModel はそれ自身はデータを保存せず、ビューがデータにアクセスするためのインターフェイスを提示しているに過ぎないということを覚えておくことです。最小限の読み取り専用モデルであれば、インターフェースのほとんどにデフォルトの実装があるので、いくつかの関数を実装するだけで済みます。クラス宣言は以下の通りです:

class StringListModel : public QAbstractListModel
{
    Q_OBJECT

public:
    StringListModel(const QStringList &strings, QObject *parent = nullptr)
        : QAbstractListModel(parent), stringList(strings) {}

    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role) const override;
    QVariant headerData(int section, Qt::Orientation orientation,
                        int role = Qt::DisplayRole) const override;

private:
    QStringList stringList;
};

rowCount ()はモデルの行数を返し、data ()は指定されたモデルのインデックスに対応するデータの項目を返します。

お行儀の良いモデルは、headerData()も実装し、ツリービューやテーブルビューのヘッダーに何か表示できるようにしています。

これは非階層モデルなので、親子関係を気にする必要がないことに注意してください。もしこのモデルが階層構造であれば、index() とparent() 関数も実装しなければなりません。

文字列のリストは、stringList プライベート・メンバ変数に内部的に格納されます。

モデルの次元

モデルの行数は、文字列リストの文字列数と同じにしたい。このことを念頭に置いて、rowCount ()関数を実装します:

int StringListModel::rowCount(const QModelIndex &parent) const
{
    return stringList.count();
}

モデルは非階層的なので、親アイテムに対応するモデルインデックスを安全に無視することができます。デフォルトでは、QAbstractListModel から派生したモデルは1つの列しか含まないので、columnCount() 関数を再実装する必要はありません。

モデルヘッダーとデータ

ビューのアイテムについては、文字列リストの文字列を返します。data() 関数は、index 引数に対応するデータ項目を返します:

QVariant StringListModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (index.row() >= stringList.size())
        return QVariant();

    if (role == Qt::DisplayRole)
        return stringList.at(index.row());
    else
        return QVariant();
}

提供されたモデルインデックスが有効で、行番号が文字列リストの項目の範囲内にあり、要求されたロールがサポートするものである場合にのみ、有効なQVariant を返します。

QTreeViewQTableView のような一部のビューでは、項目データとともにヘッダを表示することができます。モデルがヘッダを持つビューに表示される場合、ヘッダに行番号と列番号を表示させたいと思います。headerData() 関数をサブクラス化することで、ヘッダーに関する情報を提供することができます:

QVariant StringListModel::headerData(int section, Qt::Orientation orientation,
                                     int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();

    if (orientation == Qt::Horizontal)
        return QStringLiteral("Column %1").arg(section);
    else
        return QStringLiteral("Row %1").arg(section);
}

ここでも、ロールがサポートするものである場合にのみ、有効なQVariant を返します。返す正確なデータを決定する際には、ヘッダの向きも考慮されます。

すべてのビューがアイテムデータとともにヘッダを表示するわけではありません。それでも、モデルによって提供されるデータに関する関連情報を提供するために、headerData() 関数を実装することをお勧めします。

項目はいくつかの役割を持つことができ、指定された役割に応じて異なるデータを提供します。このモデルの項目は、DisplayRole という1つのロールしか持たないので、指定されたロールに関係なく項目のデータを返します。しかし、DisplayRole のために提供したデータを、ビューがツールチップでアイテムに関する情報を表示するために使用できるToolTipRole のような、他の役割で再利用することができます。

編集可能なモデル

読み取り専用モデルは、単純な選択肢がどのようにユーザーに表示されるかを示していますが、多くのアプリケーションでは、編集可能なリストモデルの方がはるかに便利です。read-only用に実装したdata()関数を変更し、flags()とsetData()の2つの追加関数を実装することで、項目を編集可能にするためにread-onlyモデルを変更することができます。以下の関数宣言をクラス定義に追加します:

    Qt::ItemFlags flags(const QModelIndex &index) const override;
    bool setData(const QModelIndex &index, const QVariant &value,
                 int role = Qt::EditRole) override;

モデルを編集可能にする

デリゲートはエディタを作成する前に、アイテムが編集可能かどうかをチェックします。モデルはその項目が編集可能であることをデリゲートに知らせなければなりません。この場合、すべての項目を有効にし、選択可能かつ編集可能にします:

Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::ItemIsEnabled;

    return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}

デリゲートが実際の編集処理をどのように行うかを知る必要はないことに注意してください。デリゲートがモデルのデータを設定する方法を提供するだけでよいことに注意してください。これはsetData() 関数によって実現されます:

bool StringListModel::setData(const QModelIndex &index,
                              const QVariant &value, int role)
{
    if (index.isValid() && role == Qt::EditRole) {

        stringList.replace(index.row(), value.toString());
        emit dataChanged(index, index, {role});
        return true;
    }
    return false;
}

このモデルでは、モデルのインデックスに対応する文字列リストの項目が、指定された値で置き換えられます。しかし、文字列リストを変更する前に、インデックスが有効であること、項目が正しい型であること、そしてロールがサポートされていることを確認しなければなりません。標準のitem delegateで使用される役割であるため、慣習上、roleはEditRole 。しかし、ブーリアン値の場合は、Qt::CheckStateRole を使用し、Qt::ItemIsUserCheckable フラグを設定します。チェックボックスは値の編集に使用されます。このモデルの基礎となるデータはすべてのロールで同じであるため、この詳細はモデルを標準コンポーネントと統合しやすくするだけです。

データが設定されると、モデルはいくつかのデータが変更されたことをビューに知らせなければなりません。これはdataChanged() シグナルを発行することで行います。変更されたデータは1つの項目だけなので、シグナルで指定される項目の範囲は1つのモデルインデックスだけに制限されます。

また、Qt::EditRole テストを追加するために data() 関数を変更する必要があります:

QVariant StringListModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (index.row() >= stringList.size())
        return QVariant();

    if (role == Qt::DisplayRole || role == Qt::EditRole)
        return stringList.at(index.row());
    else
        return QVariant();
}

行の挿入と削除

モデルの行や列の数を変更することは可能です。文字列リストモデルでは、行の数を変更することだけが理にかなっているので、行の挿入と削除の関数だけを再実装します。これらはクラス定義で宣言されています:

    bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
    bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;

このモデルの行はリスト内の文字列に対応するので、insertRows() 関数は、指定された位置の前に、文字列リストに空の文字列をいくつも挿入します。挿入される文字列の数は、指定された行の数と同じです。

親インデックスは通常、モデルのどの位置に行を追加するかを決定するために使用されます。この場合、文字列のトップレベルのリストは1つしかないので、そのリストに空の文字列を挿入するだけです。

bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent)
{
    beginInsertRows(QModelIndex(), position, position+rows-1);

    for (int row = 0; row < rows; ++row) {
        stringList.insert(position, "");
    }

    endInsertRows();
    return true;
}

モデルはまず、beginInsertRows ()関数を呼び出し、行の数が変更されようとしていることを他のコンポーネントに知らせます。この関数は、挿入される新しい行の最初と最後の行番号と、その親項目のモデルインデックスを指定します。文字列リストを変更した後、endInsertRows ()を呼び出して操作を完了し、モデルの寸法が変更されたことを他のコンポーネントに知らせます。

モデルから行を削除する関数も簡単に書くことができます。モデルから削除される行は、与えられた位置と行数によって指定されます。実装を簡単にするために親インデックスは無視し、文字列リストから対応する項目を削除するだけです。

bool StringListModel::removeRows(int position, int rows, const QModelIndex &parent)
{
    beginRemoveRows(QModelIndex(), position, position+rows-1);

    for (int row = 0; row < rows; ++row) {
        stringList.removeAt(position);
    }

    endRemoveRows();
    return true;
}

beginRemoveRows() 関数は、基礎となるデータが削除される前に常に呼び出され、削除される最初と最後の行を指定します。これにより、データが利用できなくなる前に、他のコンポーネントがデータにアクセスできるようになります。行が削除された後、モデルはendRemoveRows() を発行して処理を終了し、モデルの寸法が変更されたことを他のコンポーネントに知らせます。

次のステップ

このモデル、あるいは他のモデルによって提供されるデータを、QListView クラスを使用して、モデルの項目を縦リストの形で表示することができます。文字列リストモデルの場合、このビューはデフォルトのエディタも提供するので、項目を操作することができます。標準のビュークラスによって利用可能になる可能性については、ビュークラスで検討します。

Model Subclassing Referenceドキュメントでは、QAbstractItemModel サブクラスの要件についてより詳しく説明し、異なるタイプのモデルで様々な機能を有効にするために実装しなければならない仮想関数のガイドを提供します。

アイテムビュー便利クラス

アイテムベースのウィジェットは、その用途を反映した名前を持っています。QListWidget はアイテムのリストを提供し、QTreeWidget は複数レベルのツリー構造を表示し、QTableWidget はセルアイテムのテーブルを提供します。各クラスは、QAbstractItemView クラスの動作を継承しています。このクラスは、アイテムの選択とヘッダ管理の共通動作を実装しています。

リスト・ウィジェット

QListWidget QListWidgetItemリスト・ウィジェットは、他のウィジェットと同じように構成されます:

    QListWidget *listWidget = new QListWidget(this);

リスト項目は、リスト・ウィジェットの構築時に直接追加できます:

    new QListWidgetItem(tr("Sycamore"), listWidget);
    new QListWidgetItem(tr("Chestnut"), listWidget);
    new QListWidgetItem(tr("Mahogany"), listWidget);

リスト項目は、作成時にリスト・ウィジェットに直接追加できます。また、親リスト・ウィジェットなしで作成し、後でリストに追加することもできます:

    QListWidgetItem *newItem = new QListWidgetItem;
    newItem->setText(itemText);
    listWidget->insertItem(row, newItem);

リスト内の各アイテムは、テキストラベルとアイコンを表示できます。テキストのレンダリングに使用する色とフォントは、アイテムの外観をカスタマイズするために変更できます。ツールチップ、ステータスチップ、"What's This? "ヘルプはすべて、リストがアプリケーションに適切に統合されるように簡単に設定できます。

    newItem->setToolTip(toolTipText);
    newItem->setStatusTip(toolTipText);
    newItem->setWhatsThis(whatsThisText);

デフォルトでは、リスト内のアイテムは作成順に表示されます。項目のリストは、Qt::SortOrder で指定された基準に従って、アルファベット順または逆順に並べ替えられます:

    listWidget->sortItems(Qt::AscendingOrder);
    listWidget->sortItems(Qt::DescendingOrder);

ツリーウィジェット

アイテムのツリーまたは階層リストは、QTreeWidgetQTreeWidgetItem クラスによって提供されます。ツリーウィジェットの各アイテムは、それ自身の子アイテムを持つことができ、いくつかのカラムの情報を表示することができます。ツリーウィジェットは、他のウィジェットと同様に作成されます:

    QTreeWidget *treeWidget = new QTreeWidget(this);

アイテムをツリーウィジェットに追加する前に、カラム数を設定する必要があります。例えば、2 つのカラムを定義し、各カラムの上部にラベルを表示するヘッダを作成できます:

    treeWidget->setColumnCount(2);
    QStringList headers;
    headers << tr("Subject") << tr("Default");
    treeWidget->setHeaderLabels(headers);

各セクションのラベルを設定する最も簡単な方法は、文字列リストを提供することです。各セクションのラベルを設定する最も簡単な方法は、文字列リストを提供することです。より洗練されたヘッダーを作成するには、ツリーアイテムを作成し、好きなように装飾して、それをツリーウィジェットのヘッダーとして使用します。

ツリーウィジェットのトップレベル項目は、ツリーウィジェットを親ウィジェットとして構築されます。これらのアイテムは任意の順序で挿入できますが、各アイテムを構築するときに前のアイテムを指定することで、特定の順序でリストされるようにすることもできます:

    QTreeWidgetItem *cities = new QTreeWidgetItem(treeWidget);
    cities->setText(0, tr("Cities"));
    QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities);
    osloItem->setText(0, tr("Oslo"));
    osloItem->setText(1, tr("Yes"));

    QTreeWidgetItem *planets = new QTreeWidgetItem(treeWidget, cities);

ツリーウィジェットは、トップレベルのアイテムと、ツリー内のより深い位置にある他のアイテムの扱いが若干異なります。アイテムは、ツリーウィジェットのtakeTopLevelItem() 関数を呼び出すことでツリーのトップレベルから削除できますが、下位レベルのアイテムは、親アイテムのtakeChild() 関数を呼び出すことで削除されます。ツリーの最上位レベルには、insertTopLevelItem() 関数でアイテムが挿入されます。ツリーの下位レベルでは、親アイテムのinsertChild() 関数が使用されます。

ツリーの最上位と最下位の間でアイテムを移動させるのは簡単です。アイテムがトップレベル・アイテムであるかどうかをチェックするだけでよく、この情報は各アイテムのparent() 関数によって提供されます。例えば、ツリーウィジェットの現在のアイテムは、その位置に関係なく削除することができます:

    QTreeWidgetItem *parent = currentItem->parent();
    int index;

    if (parent) {
        index = parent->indexOfChild(treeWidget->currentItem());
        delete parent->takeChild(index);
    } else {
        index = treeWidget->indexOfTopLevelItem(treeWidget->currentItem());
        delete treeWidget->takeTopLevelItem(index);
    }

ツリーウィジェットの他の場所にアイテムを挿入する場合も、同じパターンに従います:

    QTreeWidgetItem *parent = currentItem->parent();
    QTreeWidgetItem *newItem;
    if (parent)
        newItem = new QTreeWidgetItem(parent, treeWidget->currentItem());
    else
        newItem = new QTreeWidgetItem(treeWidget, treeWidget->currentItem());

テーブル・ウィジェット

スプレッドシート・アプリケーションで見られるような項目の表は、QTableWidgetQTableWidgetItem で構築できます。これらは、ヘッダとその中で使用するアイテムを持つスクロールテーブルウィジェットを提供します。

表は、設定された行数と列数で作成することもできるし、サイズの決まっていない表に必要に応じて追加することもできる。

    QTableWidget *tableWidget;
    tableWidget = new QTableWidget(12, 3, this);

項目は、必要な位置にテーブルに追加される前に、テーブルの外側で作成されます:

    QTableWidgetItem *newItem = new QTableWidgetItem(tr("%1").arg(
        pow(row, column+1)));
    tableWidget->setItem(row, column, newItem);

水平および垂直のヘッダーは、テーブルの外側で項目を作成し、それをヘッダーとして使用することで、テーブルに追加することができます:

    QTableWidgetItem *valuesHeaderItem = new QTableWidgetItem(tr("Values"));
    tableWidget->setHorizontalHeaderItem(0, valuesHeaderItem);

表の行と列はゼロから始まることに注意。

一般的な機能

各コンビニエンス・クラスに共通するアイテム・ベースの機能がいくつかあり、各クラスで同じインターフェイスを通して利用できる。以下のセクションで、さまざまなウィジェットの例とともに、これらを紹介します。使用される各機能の詳細については、各ウィジェットのモデル/ビュークラスのリストを見てください。

隠し項目

アイテムビューウィジェットのアイテムを削除するのではなく、非表示にすることができると便利な場合があります。上記のすべてのウィジェットのアイテムは非表示にすることができ、後で再び表示することができます。アイテムが非表示になっているかどうかは、isItemHidden() 関数を呼び出すことで判断できます。アイテムはsetItemHidden() で非表示にできます。

この操作はアイテムベースなので、3つの便利なクラスすべてで同じ関数が利用できます。

選択

アイテムが選択される方法は、ウィジェットの選択モード(QAbstractItemView::SelectionMode)によって制御されます。このプロパティは、ユーザーが1つの項目を選択できるか、多くの項目を選択できるかを制御し、多くの項目を選択する場合、選択項目が連続した範囲でなければならないかどうかを制御します。選択モードは、上記のすべてのウィジェットで同じように動作します。

単一項目の選択:ユーザーがウィジェットから単一のアイテムを選択する必要がある場合、デフォルトのSingleSelection モードが最も適しています。このモードでは、現在のアイテムと選択されたアイテムは同じです。

複数項目の選択:このモードでは、排他的でないチェックボックスが独立してトグルできるのと同じように、ユーザーは既存の選択項目を変更することなく、ウィジェットの任意の項目の選択状態をトグルすることができます。

拡張選択:スプレッドシートのように、隣接する多くの項目を選択する必要があるウィジェットでは、ExtendedSelection モードが必要です。このモードでは、マウスとキーボードの両方で、ウィジェット内の連続したアイテムの範囲を選択できます。修飾キーを使用すれば、ウィジェット内の他の選択項目に隣接しない多数の項目を含む複雑な選択も作成できます。

ユーザーが修飾キーを使わずに項目を選択した場合、既存の選択はクリアされます。

ウィジェット内の選択された項目は、selectedItems() 関数を使用して読み込まれ、反復処理できる関連項目のリストを提供します。例えば、以下のコードで、選択項目のリスト内のすべての数値の合計を求めることができます:

    const QList<QTableWidgetItem *> selected = tableWidget->selectedItems();
    int number = 0;
    double total = 0;

    for (QTableWidgetItem *item : selected) {
        bool ok;
        double value = item->text().toDouble(&ok);

        if (ok && !item->text().isEmpty()) {
            total += value;
            number++;
        }
    }

単一選択モードでは、現在の項目が選択範囲に入ることに注意してください。複数選択モードや拡張選択モードでは、ユーザーがどのように選択したかに応じて、現在の項目が選択範囲に含まれないことがあります。

検索

アイテムビューウィジェット内でアイテムを検索できることは、開発者として、あるいはユーザーに提供するサービスとして、しばしば役に立ちます。3つのアイテムビューコンビニエンスクラスはすべて、これを可能な限り一貫性のあるシンプルなものにするために、共通のfindItems() 関数を提供します。

アイテムは、Qt::MatchFlags からの値の選択によって指定された基準に従って、含まれるテキストによって検索されます。findItems() 関数を使用して、一致するアイテムのリストを取得できます:

    const QList<QTreeWidgetItem *> found = treeWidget->findItems(
        itemText, Qt::MatchWildcard);

    for (QTreeWidgetItem *item : found) {
        item->setSelected(true);
        // Show the item->text(0) for each item.
    }

上記のコードは、ツリーウィジェットのアイテムが検索文字列で指定されたテキストを含む場合に選択されるようにします。このパターンは、リストウィジェットとテーブルウィジェットでも使用できます。

アイテムビューでドラッグ&ドロップを使う

Qt のドラッグアンドドロップ・インフラストラクチャは、モデル/ビュー・フレームワークで完全にサポートされています。リスト、テーブル、ツリーのアイテムはビュー内でドラッグすることができ、データは MIME エンコードされたデータとしてインポート、エクスポートすることができます。

標準的なビューは、アイテムを移動して表示順序を変更する内部ドラッグ&ドロップを自動的にサポートします。デフォルトでは、これらのビューは最もシンプルで一般的な用途向けに設定されているため、ドラッグ&ドロップは有効になっていません。アイテムをドラッグできるようにするには、ビューの特定のプロパティを有効にし、アイテム自体もドラッグできるようにする必要があります。

ビューから項目をエクスポートすることのみを許可し、そこにデータをドロップすることを許可しないモデルの要件は、完全に有効なドラッグ&ドロップモデルの要件よりも少なくなります。

新しいモデルでドラッグ&ドロップのサポートを有効にする方法については、モデルのサブクラスリファレンスも参照してください。

コンビニエンス・ビューの使用

QListWidgetQTableWidgetQTreeWidget で使用されるアイテムの各タイプは、デフォルトで異なるフラグのセットを使用するように設定されています。例えば、QListWidgetItem またはQTreeWidgetItem は、初期状態では有効で、チェック可能で、選択可能で、ドラッグ・アンド・ドロップ操作のソースとして使用できます。QTableWidgetItem は、編集可能で、ドラッグ・アンド・ドロップ操作のターゲットとして使用することもできます。

すべての標準アイテムには、ドラッグ&ドロップのためのフラグの一方または両方が設定されていますが、通常、ドラッグ&ドロップの組み込みサポートを利用するには、ビュー自体にさまざまなプロパティを設定する必要があります:

  • アイテムのドラッグを有効にするには、ビューのdragEnabled プロパティをtrue に設定します。
  • アイテムのドラッグを有効にするには、ビューのviewport プロパティをacceptDrops に設定します。ビューの内部または外部のアイテムをドロップするには、ビューの () の プロパティをtrue に設定します。
  • 現在ドラッグされているアイテムがドロップされた場合、どこに配置されるかをユーザに表示するには、ビューのshowDropIndicator プロパティを設定します。これにより、ビュー内のアイテムの配置に関する継続的に更新される情報がユーザに提供されます。

例えば、リスト・ウィジェットでドラッグ・アンド・ドロップを有効にするには、次のようなコードを記述します:

QListWidget *listWidget = new QListWidget(this);
listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
listWidget->setDragEnabled(true);
listWidget->viewport()->setAcceptDrops(true);
listWidget->setDropIndicatorShown(true);

その結果、リスト・ウィジェットは、ビュー内でアイテムをコピーできるようになり、同じタイプのデータを含むビュー間でアイテムをドラッグできるようになります。どちらの場合でも、項目は移動されるのではなく、コピーされます。

ユーザーがビュー内で項目を移動できるようにするには、リストウィジェットのdragDropMode を設定する必要があります:

listWidget->setDragDropMode(QAbstractItemView::InternalMove);

モデル/ビュークラスの使用

ドラッグ&ドロップのためのビューの設定は、コンビニエンス・ビューで使用されるのと同じパターンに従います。例えば、QListView は、QListWidget と同じように設定できます:

QListView *listView = new QListView(this);
listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
listView->setDragEnabled(true);
listView->setAcceptDrops(true);
listView->setDropIndicatorShown(true);

ビューによって表示されるデータへのアクセスはモデルによって制御されるので、使用されるモデルもドラッグ&ドロップ操作をサポートしなければなりません。モデルがサポートするアクションは、QAbstractItemModel::supportedDropActions ()関数を再実装することで指定できます。例えば、コピーと移動の操作は以下のコードで有効になります:

Qt::DropActions DragDropListModel::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction;
}

Qt::DropActions 、どのような値の組み合わせでも指定できますが、モデルはそれらをサポートするように記述する必要があります。例えば、Qt::MoveAction をリストモデルで適切に使用するためには、QAbstractItemModel::removeRows() の実装を、モデルが直接、あるいは基底クラスから継承して提供する必要があります。

アイテムのドラッグ&ドロップの有効化

モデルは、QAbstractItemModel::flags() 関数を再実装して適切なフラグを提供することで、どのアイテムがドラッグ可能で、どのアイテムがドロップを受け付けるかをビューに示します。

例えば、QAbstractListModel に基づいた単純なリストを提供するモデルは、返されるフラグがQt::ItemIsDragEnabledQt::ItemIsDropEnabled の値を含むようにすることで、各アイテムのドラッグ&ドロップを有効にすることができます:

Qt::ItemFlags DragDropListModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags defaultFlags = QStringListModel::flags(index);

    if (index.isValid())
        return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
    else
        return Qt::ItemIsDropEnabled | defaultFlags;
}

アイテムはモデルのトップレベルにドロップすることができますが、ドラッグは有効なアイテムに対してのみ有効であることに注意してください。

上記のコードでは、モデルがQStringListModel から派生しているため、その実装である flags() 関数を呼び出すことで、フラグのデフォルト・セットを取得しています。

エクスポートされたデータのエンコード

ドラッグ&ドロップ操作でモデルからデータがエクスポートされると、1つ以上のMIMEタイプに対応する適切なフォーマットにエンコードされます。モデルは、QAbstractItemModel::mimeTypes() 関数を再実装し、標準的な MIME タイプのリストを返すことで、アイテムを提供するために使用できる MIME タイプを宣言します。

例えば、プレーンテキストのみを提供するモデルは、以下の実装を提供します:

QStringList DragDropListModel::mimeTypes() const
{
    QStringList types;
    types << "application/vnd.text.list";
    return types;
}

モデルはまた、広告されたフォーマットでデータをエンコードするコードを提供しなければなりません。これは、他のドラッグ&ドロップ操作と同じように、QMimeData オブジェクトを提供するためにQAbstractItemModel::mimeData() 関数を再実装することで実現されます。

以下のコードは、与えられたインデックスのリストに対応するデータの各項目が、どのようにプレーンテキストとしてエンコードされ、QMimeData オブジェクトに格納されるかを示しています。

QMimeData *DragDropListModel::mimeData(const QModelIndexList &indexes) const
{
    QMimeData *mimeData = new QMimeData;
    QByteArray encodedData;

    QDataStream stream(&encodedData, QIODevice::WriteOnly);

    for (const QModelIndex &index : indexes) {
        if (index.isValid()) {
            QString text = data(index, Qt::DisplayRole).toString();
            stream << text;
        }
    }

    mimeData->setData("application/vnd.text.list", encodedData);
    return mimeData;
}

モデルインデックスのリストが関数に供給されるので、このアプローチは階層モデルでも非階層モデルでも使用できるほど一般的です。

カスタムデータ型は、meta objects として宣言する必要があり、ストリーム演算子を実装する必要があることに注意してください。詳細はQMetaObject クラスの説明を参照してください。

ドロップされたデータのモデルへの挿入

任意のモデルが取りこぼしたデータをどのように扱うかは、そのデータの型(リスト、テーブル、ツリー)と、その内容がユーザーにどのように表示されるかに依存します。一般的に、取りこぼしたデータを処理するために取られるアプローチは、モデルの基礎となるデータストアに最も適したものであるべきです。

モデルの種類によって、ドロップされたデータの扱い方は異なります。リストモデルとテーブルモデルは、データの項目が格納されるフラットな構造を提供するだけです。その結果、ビュー内の既存の項目にデータがドロップされると、新しい行(および列)が挿入されたり、供給されたデータの一部を使用してモデル内の項目の内容が上書きされたりします。ツリーモデルは多くの場合、新しいデータを含む子アイテムをその基礎となるデータストアに追加することができ、したがってユーザーに関する限り、より予測可能な振る舞いをします。

削除されたデータは、モデルのQAbstractItemModel::dropMimeData ()の再実装によって処理されます。例えば、文字列の単純なリストを扱うモデルは、既存のアイテムにドロップされたデータを、モデルのトップレベルにドロップされたデータ(つまり、無効なアイテムにドロップされたデータ)とは別に扱う実装を提供することができます。

モデルは、QAbstractItemModel::canDropMimeData ()を再実装することで、特定のアイテムへのドロップを禁止したり、ドロップされたデータによってドロップを禁止したりすることができます。

モデルはまず、操作が実行されるべきものであること、供給されたデータが使用可能な形式であること、そしてモデル内での移動先が有効であることを確認する必要があります:

bool DragDropListModel::canDropMimeData(const QMimeData *data,
    Qt::DropAction action, int row, int column, const QModelIndex &parent) const
{
    Q_UNUSED(action);
    Q_UNUSED(row);
    Q_UNUSED(parent);

    if (!data->hasFormat("application/vnd.text.list"))
        return false;

    if (column > 0)
        return false;

    return true;
}
bool DragDropListModel::dropMimeData(const QMimeData *data,
    Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    if (!canDropMimeData(data, action, row, column, parent))
        return false;

    if (action == Qt::IgnoreAction)
        return true;

単純な1列の文字列リストモデルでは、与えられたデータがプレーンテキストでない場合や、ドロップのために与えられた列番号が無効である場合に、失敗を示すことができます。

モデルに挿入されるデータは、既存の項目にドロップされるかどうかによって扱いが異なります。この単純な例では、既存の項目の間、リストの最初の項目の前、最後の項目の後へのドロップを許可したいとします。

ドロップが発生すると、親アイテムに対応するモデルインデックスは、ドロップがアイテム上で発生したことを示す有効なものになるか、または、ドロップがモデルのトップレベルに対応するビューのどこかで発生したことを示す無効なものになります。

    int beginRow;

    if (row != -1)
        beginRow = row;

まず、親インデックスが有効かどうかに関係なく、モデルにアイテムを挿入するために使用できるかどうかを確認するために、提供された行番号を調べます。

    else if (parent.isValid())
        beginRow = parent.row();

親モデルのインデックスが有効であれば、項目のドロップが発生します。この単純なリストモデルでは、項目の行番号を調べ、その値を使ってドロップされた項目をモデルの最上位に挿入します。

    else
        beginRow = rowCount(QModelIndex());

ビューの他の場所でドロップが発生し、行番号が使用できない場合、モデルの最上位レベルに項目を追加します。

階層モデルでは、あるアイテムにドロップが発生した場合、そのアイテムの子として新しいアイテムをモデルに挿入する方が良いでしょう。ここで示す単純な例では、モデルは1階層しかないので、この方法は適切ではありません。

インポートされたデータのデコード

dropMimeData() の各実装は、データをデコードし、モデルの基礎となるデータ構造に挿入しなければなりません。

単純な文字列リスト・モデルの場合、エンコードされた項目をデコードしてQStringList にストリームすることができる:

    QByteArray encodedData = data->data("application/vnd.text.list");
    QDataStream stream(&encodedData, QIODevice::ReadOnly);
    QStringList newItems;
    int rows = 0;

    while (!stream.atEnd()) {
        QString text;
        stream >> text;
        newItems << text;
        ++rows;
    }

その後、文字列を基礎となるデータストアに挿入することができる。一貫性を保つために、これはモデル自身のインターフェースを通して行うことができます:

    insertRows(beginRow, rows, QModelIndex());
    for (const QString &text : std::as_const(newItems)) {
        QModelIndex idx = index(beginRow, 0, QModelIndex());
        setData(idx, text);
        beginRow++;
    }

    return true;
}

モデルは通常、QAbstractItemModel::insertRows ()とQAbstractItemModel::setData ()関数の実装を提供する必要があることに注意してください。

プロキシモデル

モデル/ビューフレームワークでは、1つのモデルから提供されるデータ項目を、いくつものビューで共有することができます。カスタムビューとデリゲートは、同じデータに対して根本的に異なる表現を提供する効果的な方法です。しかし、アプリケーションは同じデータを加工した従来のビューを提供する必要があることがよくあります。

ソートとフィルタリングをビューの内部機能として実行することは適切であるように思えますが、このアプローチでは、複数のビューがそのような潜在的にコストのかかる操作の結果を共有することはできません。モデル自体の内部でソートを行う別のアプローチでは、各ビューが最新の処理操作に従って整理されたデータ項目を表示しなければならないという同様の問題が発生します。

この問題を解決するために、モデル/ビューフレームワークでは、個々のモデルとビューの間で提供される情報を管理するためにプロキシモデルを使用します。プロキシモデルとは、ビューから見ると通常のモデルのように振る舞い、ビューに代わってソースモデルからデータにアクセスするコンポーネントです。モデル/ビューフレームワークによって使用されるシグナルとスロットは、それ自身とソースモデルの間にいくつのプロキシモデルが配置されていても、各ビューが適切に更新されることを保証します。

プロキシモデルの使用

プロキシモデルは、既存のモデルと任意の数のビューの間に挿入することができます。Qt には標準のプロキシ・モデルQSortFilterProxyModel が用意されており、通常はこれをインスタンス化して直接使用しますが、サブクラス化してカスタムのフィルタリングやソート動作を提供することもできます。QSortFilterProxyModel クラスは次のように使用できます:

    QSortFilterProxyModel *filterModel = new QSortFilterProxyModel(parent);
    filterModel->setSourceModel(stringListModel);

    QListView *filteredView = new QListView;
    filteredView->setModel(filterModel);

プロキシモデルはQAbstractItemModel を継承しているので、どのような種類のビューにも接続でき、ビュー間で共有することもできます。また、他のプロキシ・モデルから取得した情報をパイプラインで処理することもできます。

QSortFilterProxyModel クラスはインスタンス化され、アプリケーションで直接使用されるように設計されています。このクラスをサブクラス化し、必要な比較操作を実装することで、より特殊なプロキシモデルを作成することができます。

プロキシモデルのカスタマイズ

一般的に、プロキシ・モデルで使用される処理の種類は、データの各項目をソース・モデルの元の場所からプロキシ・モデルの別の場所にマッピングすることを含みます。モデルによっては、プロキシモデル内に対応する場所がない項目もあります。このようなモデルはフィルタリングプロキシモデルです。ビューは、プロキシ モデルによって提供されるモデル インデックスを使用して項目にアクセスしますが、これにはソース モデルやそのモデル内の元の項目の位置に関する情報は含まれていません。

QSortFilterProxyModel は、ソースモデルからのデータをビューに供給する前にフィルタリングすることを可能にし、また、ソースモデルの内容をソート済みデータとしてビューに供給することを可能にします。

カスタムフィルタリングモデル

QSortFilterProxyModel クラスは、かなり多用途で、様々な一般的な状況で使用できるフィルタリングモデルを提供します。上級ユーザのために、QSortFilterProxyModel はサブクラス化することができ、カスタムフィルタを実装できるメカニズムを提供します。

QSortFilterProxyModel のサブクラスは、プロキシモデルからのモデルインデックスが要求されたり使用されたりするたびに呼び出される2つの仮想関数を再実装することができます:

  • filterAcceptsColumn() は、ソースモデルの一部から特定の列をフィルタリングするために使われます。
  • filterAcceptsRow() は、ソースモデルの一部から特定の列をフィルタリングするために使用されます。

QSortFilterProxyModel における上記の関数のデフォルト実装は、すべてのアイテムがビューに渡されるように true を返します。これらの関数を再実装する場合は、個々の行や列をフィルタリングするために false を返す必要があります。

カスタムソートモデル

QSortFilterProxyModel インスタンスは、std::stable_sort() 関数を使用して、ソースモデルのアイテムとプロキシモデルのアイテムの間のマッピングを設定し、ソースモデルの構造を変更することなく、アイテムのソートされた階層をビューに公開できるようにします。カスタムのソート動作を提供するには、 () 関数を再実装して、カスタムの比較を実行します。lessThan

モデルサブクラスリファレンス

モデルのサブクラスは、QAbstractItemModel ベースクラスで定義されている多くの仮想関数の実装を提供する必要があります。実装する必要があるこれらの関数の数は、単純なリスト、テーブル、またはアイテムの複雑な階層のビューを提供するかどうかというモデルのタイプによって異なります。QAbstractListModelQAbstractTableModel を継承したモデルは、これらのクラスが提供する関数のデフォルト実装を利用することができます。ツリー構造のデータ項目を公開するモデルは、QAbstractItemModel の多くの仮想関数の実装を提供しなければなりません。

モデルのサブクラスで実装する必要がある関数は、3つのグループに分けることができます:

  • 項目データの処理:項目のデータ処理: すべてのモデルは、ビューとデリゲートがモデルの次元を照会し、項目を調べ、データを取得できるようにする関数を実装する必要があります。
  • ナビゲーションとインデックスの作成:階層モデルは、ビューがツリー状の構造をナビゲートし、アイテムのモデルインデックスを取得するために呼び出すことができる関数を提供する必要があります。
  • ドラッグ&ドロップのサポートとMIMEタイプの処理:モデルは、内部および外部からのドラッグ&ドロップ操作の実行方法を制御する関数を継承します。これらの関数により、他のコンポーネントやアプリケーションが理解できるMIMEタイプでデータ項目を記述することができます。

アイテム・データの処理

モデルは、提供するデータへのさまざまなレベルのアクセスを提供することができる:単純な読み取り専用コンポーネントであることもあれば、サイズ変更操作をサポートするモデルもあり、アイテムの編集を可能にするモデルもあります。

読み取り専用アクセス

モデルによって提供されるデータへの読み取り専用アクセスを提供するためには、以下の関数をモデルのサブクラスに実装しなければなりません

flags()モデルによって提供される各項目に関する情報を取得するために、他のコンポーネントによって使用されます。多くのモデルでは、フラグの組み合わせにQt::ItemIsEnabledQt::ItemIsSelectable を含める必要があります。
data()ビューとデリゲートにアイテムデータを提供するために使われます。一般的に、モデルがデータを提供する必要があるのは、Qt::DisplayRole とアプリケーション固有のユーザーロールだけですが、Qt::ToolTipRoleQt::AccessibleTextRoleQt::AccessibleDescriptionRole のデータを提供することも良い習慣です。各ロールに関連する型については、Qt::ItemDataRole enum documentationを参照してください。
headerData()ヘッダに表示する情報をビューに提供します。この情報は、ヘッダー情報を表示できるビューによってのみ取得されます。
rowCount()モデルによって公開されるデータの行数を提供します。

これらの4つの関数は、リストモデル(QAbstractListModel サブクラス)やテーブルモデル(QAbstractTableModel サブクラス)を含む、すべてのタイプのモデルで実装されなければなりません。

さらに、QAbstractTableModel およびQAbstractItemModel の直接のサブクラスでは、以下の関数を実装しなければなりません

columnCount()モデルによって公開されるデータの列数を提供します。リストモデルはこの関数を提供しません。なぜなら、この関数はQAbstractListModel ですでに実装されているからです。

編集可能な項目

編集可能なモデルでは、データの項目を変更することができ、行や列を挿入したり削除したりするための関数を提供することもできます。編集を可能にするためには、以下の関数が正しく実装されていなければなりません:

flags()各項目に対して適切なフラグの組み合わせを返さなければならない。特に、この関数が返す値には、読み取り専用モデルの項目に適用される値に加えて、Qt::ItemIsEditable
setData()指定されたモデルのインデックスに関連付けられたデータの項目を変更するために使用されます。ユーザーインターフェース要素によって提供されるユーザー入力を受け付けるために、この関数はQt::EditRole に関連付けられたデータを扱わなければならない。実装は、Qt::ItemDataRole によって指定された多くの異なる種類の役割に関連付けられたデータを受け入れることもできます。データの項目を変更した後、モデルはdataChanged() シグナルを発して、他のコンポーネントに変更を通知しなければならない。
setHeaderData()水平および垂直ヘッダー情報を変更するために使用される。データの項目を変更した後、モデルはheaderDataChanged() シグナルを発して、他のコンポーネントに変更を通知しなければならない。

サイズ変更可能なモデル

すべてのタイプのモデルは、行の挿入と削除をサポートすることができます。テーブルモデルと階層モデルは、列の挿入と削除もサポートすることができます。モデルの次元の変更が発生する前と 後の両方で、他のコンポーネントにその変更を通知することが重要です。その結果、以下の関数を実装することで、モデルのサイズを変更できるようになりますが、実装では、適切な関数が呼び出され、接続されたビューやデリゲートに通知されるようにしなければなりません:

insertRows()すべてのタイプのモデルに新しい行やデータ項目を追加するために使用されます。実装では、基礎となるデータ構造に新しい行を挿入する前に beginInsertRows() を呼び出し、その直後に endInsertRows() を呼び出す必要があります。
removeRows()すべての型のモデルから行とそれが含むデータ項目を削除するために使用される。実装では、基礎となるデータ構造から行が削除される前に beginRemoveRows() を呼び出し、その直後に endRemoveRows() を呼び出さなければならない。
insertColumns()テーブルモデルや階層モデルに新しい列やデータ項目を追加するために使用される。実装では、基礎となるデータ構造に新しい列を挿入する前に beginInsertColumns() を呼び出し、その直後に endInsertColumns() を呼び出す必要があります。
removeColumns()テーブルモデルや階層モデルから列とその列が含むデータ項目を削除するために使用する。実装では、列が基礎となるデータ構造から削除される前に beginRemoveColumns() を呼び出し、その直後に endRemoveColumns() を呼び出す必要があります。

一般的に、これらの関数は操作が成功した場合に真を返すはずです。しかし、操作が部分的にしか成功しない場合もあります。例えば、挿入できた行数が指定した数より少ない場合などです。そのような場合、モデルは失敗を示すfalseを返し、接続されたコンポーネントがその状況に対処できるようにすべきです。

リサイズAPIの実装で呼び出される関数が発するシグナルは、データが利用できなくなる前に、接続されたコンポーネントが対処する機会を与えます。begin関数とend関数で挿入と削除の操作をカプセル化することで、モデルがpersistent model indexes

通常、begin関数とend関数は、モデルの基本構造の変更を他のコンポーネントに通知することができます。モデルの構造に対するより複雑な変更(内部的な再編成、データの並べ替え、その他の構造的な変更など)については、以下のシーケンスを実行する必要があります:

このシーケンスは、より高レベルで便利なプロテクト・メソッドの代わりに、あらゆる構造更新に使用できます。例えば、200万行のモデルから奇数行をすべて削除する必要がある場合、各要素が1つである100万個の連続した範囲を割引くことになります。beginRemoveRowsとendRemoveRowsを100万回使用することは可能ですが、明らかに非効率的です。代わりに、必要なすべての永続インデックスを一度に更新する単一のレイアウト変更として通知することができます。

モデルデータの遅延ポピュレーション

モデルデータのレイジーポピュレーションは、モデルに関する情報の要求を、ビューで実際に必要になるまで延期することを効果的に可能にします。

モデルによっては、リモートソースからデータを取得する必要があったり、データの編成方法に関する情報を取得するために時間のかかる操作を実行する必要があったりします。ビューは一般的に、モデルデータを正確に表示するために可能な限り多くの情報を要求するため、ビューに返される情報量を制限して、不必要なデータの再要求を減らすことが有効な場合があります。

与えられた項目の子供の数を見つけることが高価な操作である階層モデルでは、モデルのrowCount ()実装が必要な場合にのみ呼び出されるようにすることが有用です。このような場合、hasChildren() 関数を再実装することで、ビューが子の存在をチェックし、QTreeView の場合、親アイテムの適切な装飾を描画する安価な方法を提供することができます。

hasChildren() の再実装がtrue またはfalse を返す場合でも、ビューがrowCount() を呼び出して子の数を調べる必要はないかもしれない。例えば、QTreeView は、親アイテムがそれらを表示するために展開されていない場合、何人の子がいるかを知る必要はありません。

多くの項目が子を持つことが分かっている場合、hasChildren() を再実装して無条件にtrue を返すようにすると便利なことがあります。こうすることで、モデルデータの初期投入を可能な限り高速に行いつつ、各アイテムを後で子について調べることができます。唯一の欠点は、存在しない子アイテムをユーザーが表示しようとするまで、子アイテムを持たないアイテムがビューによっては正しく表示されない可能性があることです。

階層モデルは、ビューが公開するツリーのような構造をナビゲートし、アイテムのモデルインデックスを取得するために呼び出すことができる関数を提供する必要があります。

親と子

ビューに公開される構造は、基礎となるデータ構造によって決定されるため、以下の関数の実装を提供することによって、独自のモデルインデックスを作成することは、各モデルのサブクラス次第です:

index()親アイテムのモデルインデックスが与えられた場合、この関数は、ビューとデリゲートがそのアイテムの子にアクセスすることを可能にします。指定された行、列、親モデルインデックスに対応する有効な子項目が見つからない場合、この関数は無効なモデルインデックスである QModelIndex() を返さなければなりません。
parent()指定された子項目の親に対応するモデルインデックスを提供します。指定されたモデル・インデックスがモデル内の最上位アイテムに対応する場合、またはモデル内に有効な親アイテムが存在しない場合、この関数は、空の QModelIndex() コンストラクタで作成された無効なモデル・インデックスを返さなければなりません。

上記の両方の関数は、他のコンポーネントが使用するインデックスを生成するためにcreateIndex() ファクトリー関数を使用します。モデル・インデックスを後で対応するアイテムと再度関連付けることができるように、この関数に何らかの一意な識別子を与えることは、モデルにとって通常のことです。

ドラッグ&ドロップのサポートと MIME タイプの処理

モデル/ビュークラスはドラッグ&ドロップ操作をサポートし、多くのアプリケーションにとって十分なデフォルトの振る舞いを提供します。しかし、ドラッグ&ドロップ操作の間にアイテムがエンコードされる方法、デフォルトでコピーされるか移動されるか、既存のモデルに挿入される方法などをカスタマイズすることも可能です。

さらに、コンビニエンス・ビュー・クラスは、既存の開発者が期待する動作に近い、特殊な動作を実装しています。コンビニエンス・ビューのセクションでは、この動作の概要を説明します。

MIME データ

デフォルトでは、組み込みモデルとビューは、モデルのインデックスに関する情報を渡すために内部 MIME タイプ (application/x-qabstractitemmodeldatalist) を使用します。これは、各項目の行番号と列番号、および各項目がサポートするロールに関する情報を含む項目リストのデータを指定します。

この MIME タイプを使用してエンコードされたデータは、QAbstractItemModel::mimeData() に、シリアライズする項目を含むQModelIndexList を指定して呼び出すことで取得できます。

カスタムモデルでドラッグ&ドロップのサポートを実装する場合、以下の関数を再実装することで、特殊な形式でデータの項目をエクスポートすることができます:

mimeData()この関数は、デフォルトのapplication/x-qabstractitemmodeldatalist 内部 MIME タイプ以外の形式でデータを返すために再実装することができます。

サブクラスは、ベースクラスからデフォルトのQMimeData オブジェクトを取得し、そこに追加のフォーマットでデータを追加することができます。

多くのモデルでは、text/plainimage/png などの MIME タイプで表される一般的な形式で項目の内容を提供すると便利です。QMimeData::setImageData(),QMimeData::setColorData(),QMimeData::setHtml() 関数を使えば、画像や色、HTML文書を簡単にQMimeData オブジェクトに追加できることに注意してください。

ドロップされたデータの受け入れ

ビュー上でドラッグ&ドロップ操作が実行されると、基礎となるモデルに問い合わせが行われ、サポートする操作の種類と、受け入れることができるMIMEタイプが決定されます。この情報はQAbstractItemModel::supportedDropActions() とQAbstractItemModel::mimeTypes() 関数によって提供されます。QAbstractItemModel によって提供される実装をオーバーライドしないモデルは、コピー操作とアイテムのデフォルト内部 MIME タイプをサポートします。

シリアライズされたアイテムデータがビューにドロップされると、そのデータはQAbstractItemModel::dropMimeData() の実装を使用して現在のモデルに挿入されます。この関数のデフォルトの実装では、モデル内のデータを上書きすることはありません。代わりに、アイテムの兄弟として、またはそのアイテムの子として、データのアイテムを挿入しようとします。

組み込みのMIMEタイプに対するQAbstractItemModel のデフォルトの実装を利用するために、新しいモデルは以下の関数の再実装を提供しなければなりません:

insertRows()これらの関数は、QAbstractItemModel::dropMimeData() によって提供される既存の実装を使用して、モデルが自動的に新しいデータを挿入することを可能にします。
insertColumns()
setData()新しい行と列に項目を入力できるようにします。
setItemData()この関数は、新しい項目の入力をより効率的にサポートします。

他の形式のデータを受け付けるには、これらの関数を再実装する必要があります:

supportedDropActions()drop actions の組み合わせを返すために使用され、モデルが受け付けるドラッグ&ドロップ操作のタイプを示します。
mimeTypes()モデルがデコードして扱うことができるMIMEタイプのリストを返すために使われます。一般的に、モデルへの入力でサポートされるMIMEタイプは、外部コンポーネントで使用するためにデータをエンコードする際に使用できるものと同じです。
dropMimeData()ドラッグ&ドロップ操作によって転送されたデータの実際のデコードを実行し、モデル内のどこに設定されるかを決定し、必要に応じて新しい行と列を挿入します。この機能がサブクラスでどのように実装されるかは、各モデルが公開するデータの要件に依存します。

dropMimeData ()関数の実装が、行や列の挿入や削除によってモデルの次元を変更する場合、またはデータの項目が変更される場合、関連するすべてのシグナルが発行されるように注意する必要があります。setData()、insertRows()、insertColumns() など、サブクラス内の他の関数の再実装を単純に呼び出すと、モデルの動作が一貫していることを確認できて便利です。

ドラッグ操作を正しく動作させるためには、モデルからデータを削除する以下の関数を再実装することが重要です:

アイテムビューでのドラッグ&ドロップの詳細については、「アイテムビューでのドラッグ&ドロップの使用」を参照してください。

便利ビュー

便利ビュー (QListWidget,QTableWidget,QTreeWidget) は、デフォルトのドラッグ&ドロップ機能をオーバーライドして、柔軟性は低いですが、多くのアプリケー ションに適した、より自然な動作を提供します。例えば、QTableWidget のセルにデータをドロップし、既存の内容を転送されるデータに置き換えることが一般的であるため、基礎となるモデルはモデルに新しい行や列を挿入するのではなく、ターゲット項目のデータを設定します。利便性ビューでのドラッグ&ドロップの詳細については、項目ビューでのドラッグ&ドロップの使用 を参照してください。

大量データのパフォーマンス最適化

canFetchMore() 関数は、親がさらに利用可能なデータを持っているかどうかをチェックし、それに応じてtrue または false を返す。fetchMore() 関数は、指定された親に基づいてデータを取得します。これらの関数は両方とも、例えば、QAbstractItemModel にデータを入力するための増分データを含むデータベース・クエリで組み合わせることができます。ここでは、canFetchMore() を再実装して、フェッチするデータがさらにあるかどうかを示し、fetchMore() を再実装して、必要に応じてモデルにデータを入力します。

別の例としては、ツリー・モデルの枝が拡張されたときにfetchMore() を再実装します。

fetchMore() を再実装してモデルに行を追加する場合は、beginInsertRows() とendInsertRows() を呼び出す必要があります。また、canFetchMore() とfetchMore() は、デフォルトの実装では false を返して何もしないため、どちらも再実装する必要があります。

モデル/ビュークラス

これらのクラスはモデル/ビュー設計パターンを使用しており、(モデル内の)基礎データと、(ビュー内の)ユーザーによるデータの表示と操作の方法は分離されています。

QAbstractItemDelegate

モデルからデータ項目を表示・編集するために使われます。

QAbstractItemModel

項目モデルクラスの抽象インターフェース

QAbstractItemView

アイテムビュークラスの基本機能

QAbstractListModel

一次元リストモデルを作るためにサブクラス化できる抽象モデル

QAbstractProxyModel

ソート、フィルタリング、その他のデータ処理タスクを行うプロキシ項目モデルの基底クラス

QAbstractTableModel

テーブルモデルを作成するためにサブクラス化できる抽象モデル

QColumnView

カラムビューのモデル/ビュー実装

QConcatenateTablesProxyModel

複数のソースモデルをプロキシし、それらの行を連結する

QDataWidgetMapper

データモデルのセクションとウィジェットのマッピング

QFileSystemModel

ローカルファイルシステム用のデータモデル

QHeaderView

項目ビューのヘッダー行またはヘッダー列

QIdentityProxyModel

ソースモデルを変更せずにプロキシする

QItemDelegate

モデルからのデータ項目の表示と編集機能

QItemEditorCreator

QItemEditorCreatorBaseをサブクラス化することなく、アイテム・エディタ作成ベースを作成できるようにする

QItemEditorCreatorBase

新しいアイテムエディタクリエータを実装する際にサブクラス化する必要がある抽象基底クラス

QItemEditorFactory

ビューやデリゲートで項目データを編集するためのウィジェット

QItemSelection

モデル内で選択された項目に関する情報を管理する

QItemSelectionModel

ビューの選択されたアイテムの情報を管理する

QItemSelectionRange

モデル内で選択された項目の範囲に関する情報を管理する

QListView

モデル上のリストまたはアイコン・ビュー

QListWidget

項目ベースのリストウィジェット

QListWidgetItem

QListWidget アイテムビュークラスで使用するアイテム

QModelIndex

データモデル内のデータを見つけるために使われます

QModelRoleData

ロールとそのロールに関連付けられたデータを保持する

QModelRoleDataSpan

QModelRoleData オブジェクトに跨ります。

QPersistentModelIndex

データモデル内のデータを見つけるために使用

QSortFilterProxyModel

別のモデルとビューの間で渡されるデータの並べ替えとフィルタリングのサポート

QStandardItem

QStandardItemModel クラスで使用されるアイテム

QStandardItemEditorCreator

QItemEditorCreatorBase をサブクラス化することなくウィジェットを登録する可能性

QStandardItemModel

カスタムデータを格納するための汎用モデル

QStringListModel

ビューに文字列を供給するモデル

QStyledItemDelegate

モデルからのデータ項目の表示と編集機能

QTableView

テーブルビューのデフォルトモデル/ビュー実装

QTableWidget

デフォルトモデルによる項目ベースのテーブルビュー

QTableWidgetItem

QTableWidgetクラスで使う項目

QTableWidgetSelectionRange

モデルのインデックスと選択モデルを使わずに、モデル内の選択を操作する方法

QTreeView

ツリービューのデフォルトモデル/ビュー実装

QTreeWidget

定義済みのツリーモデルを使用するツリービュー

QTreeWidgetItem

QTreeWidget便利クラスで使用する項目

QTreeWidgetItemIterator

QTreeWidgetインスタンス内のアイテムを反復処理する方法

©2024 The Qt Company Ltd. 本書に含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。