このページでは

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

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

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

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

モデル・ビュー・コントローラー(MVC)は、Smalltalkに由来する設計パターンであり、ユーザーインターフェースの構築によく用いられます。『デザインパターン』の中で、Gammaらは次のように述べています。

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

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

モデル、ビュー、およびデリゲートの相互作用図モデル/ビューアーキテクチャ

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

ビューはモデルからモデルインデックスを取得します。これらはデータ項目への参照です。ビューはモデルにモデルインデックスを指定することで、データソースからデータ項目を取得することができます。

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

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

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

  • モデルからのシグナルは、データソースが保持するデータの変更についてビューに通知します。
  • ビューからのシグナルは、表示されている項目に対するユーザーの操作に関する情報を提供します。
  • デリゲートからのシグナルは、編集中にエディタの状態をモデルおよびビューに伝えるために使用されます。

モデル

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

モデルに関する基本的な概念は、「モデルクラス」のセクションで説明されています。

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

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

Qt には、データ項目の処理に使用できる既製のモデルがいくつか用意されています。

  • QRangeModel は、サブクラス化を行うことなく、既存の C++ コンテナや任意の反復可能な C++ 範囲をモデル/ビューフレームワークに適応させます。
  • QStringListModel は、QString 項目の単純なリストを格納するために使用されます。
  • QStandardItemModel より複雑な項目のツリー構造を管理し、各項目には任意のデータを格納できます。
  • QFileSystemModel ローカルファイルシステム内のファイルやディレクトリに関する情報を提供します。
  • QSqlQueryModel、QSqlTableModel 、およびQSqlRelationalTableModel は、モデル/ビューの規約に従ってデータベースにアクセスするために使用されます。

これらの標準モデルが要件を満たさない場合は、QAbstractItemModel 、QAbstractListModel 、またはQAbstractTableModel をサブクラス化して、独自のカスタムモデルを作成することができます。あるいは、データがすでにC++コンテナや範囲に格納されている場合は、QRangeModel を使用することで、サブクラス化することなく適応させることができる場合が多くあります。

ビュー

さまざまな種類のビューに対して、完全な実装が用意されています。QListView は項目のリストを表示し、QTableView はモデルからのデータをテーブル形式で表示し、QTreeView はモデルのデータ項目を階層リストとして表示します。これらの各クラスは、QAbstractItemView という抽象基底クラスに基づいています。これらのクラスはそのまま使用できる実装ですが、サブクラス化してカスタマイズされたビューを提供することも可能です。

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

デリゲート

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

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

ソート

モデル/ビューアーキテクチャにおけるソートには 2 つのアプローチがあります。どちらのアプローチを選択するかは、基盤となるモデルによって異なります。

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

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

便利クラス

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

このようなクラスの例としては、QListWidget 、QTreeWidget 、QTableWidget などがあります。

これらのクラスはビュークラスほど柔軟性がなく、任意のモデルと組み合わせて使用することはできません。アイテムベースのクラスセットがどうしても必要な場合を除き、アイテムビューでのデータ処理にはモデル/ビューアプローチを使用することを推奨します。

アイテムベースのインターフェースを使い続けつつ、モデル/ビューのアプローチが提供する機能を活用したい場合は、QListView 、QTableView 、QTreeView といったビュークラスを、QStandardItemModel と組み合わせて使用することを検討してください。

モデルとビューの使用

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

Qtに組み込まれている2つのモデル

Qt が提供する標準モデルには、QStandardItemModel とQFileSystemModel の 2 つがあります。QStandardItemModel は、リスト、テーブル、ツリービューで必要となるさまざまなデータ構造を表現するために使用できる汎用モデルです。 このモデルは、データ項目も保持します。QFileSystemModel は、ディレクトリの内容に関する情報を管理するモデルです。そのため、それ自体はデータ項目を保持せず、単にローカルファイルシステム上のファイルやディレクトリを表すだけです。

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

既存のモデルでのビューの使用

QListView クラスとQTreeView クラスは、QFileSystemModel と併用するのに最も適したビューです。以下の例では、ディレクトリの内容をツリービューで表示し、その横にリストビューで同じ情報を表示しています。これらのビューはユーザーの選択内容を共有しているため、選択された項目は両方のビューでハイライト表示されます。

同じファイルシステムモデルを表示するためのツリービューとリストビュー

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 のすべてのサブクラスは、データを項目のテーブルを含む階層構造として表現します。ビューはこの規約を使用してモデル内のデータ項目にアクセスしますが、ユーザーにこの情報をどのように提示するかについては制限されません。

リストモデル、テーブルモデル、ツリーモデル

また、モデルはシグナルとスロットのメカニズムを通じて、関連付けられたビューに対してデータの変更を通知します。

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

モデルインデックス

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

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

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

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

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

}

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

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

  • モデルのディメンションは、rowCount() およびcolumnCount() を使用して取得できます。これらの関数では、通常、親モデルのインデックスを指定する必要があります。
  • モデルインデックスは、モデル内の項目にアクセスするために使用されます。項目を指定するには、行、列、および親モデルのインデックスが必要です。
  • モデル内の最上位の項目にアクセスするには、QModelIndex() を使用し、親インデックスとして null のモデルインデックスを指定します。
  • 項目には、さまざまな役割のデータが含まれています。特定の役割のデータを取得するには、モデルにモデルインデックスと役割の両方を指定する必要があります。

詳細情報

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

ビュークラス

概念

モデル/ビューアーキテクチャにおいて、ビューはモデルからデータ項目を取得し、それらをユーザーに提示します。データの提示方法は、モデルによって提供されるデータの表現と類似している必要はなく、データ項目を格納するために使用される基盤となるデータ構造とは全く異なる場合もあります。

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

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

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

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

既存のビューの使用

Qt XML には、ほとんどのユーザーにとって馴染みのある方法でモデルからのデータを表示する、すぐに使える 3 つのビュークラスが用意されています。QListView は、モデルからの項目を単純なリストとして、あるいは古典的なアイコンビューの形式で表示できます。QTreeView は、モデル内の項目をリストの階層構造として表示し、深くネストされた構造をコンパクトに表現することができます。QTableView は、スプレッドシートアプリケーションのレイアウトのように、モデル内の項目をテーブル形式で表示します。

リスト表示、ツリー表示、テーブル表示

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

モデルの使用

例として、以前に作成した文字列リストモデルを取り上げ、データを設定し、モデルの内容を表示するビューを構築します。これらはすべて、1つの関数内で実行できます:

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

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

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つのテーブルビューはモデルを共有していますが、選択モデルは共有していません

上の画像は、同じモデルに対する2つの異なるビューを示しており、それぞれに複数の選択された項目が含まれています。モデルからのデータはビュー間で一貫して表示されていますが、各ビューは独自の内部選択モデルを保持しています。これは特定の状況では有用ですが、多くのアプリケーションでは、共有された選択モデルが望ましいでしょう。

項目の選択の処理

ビュー内での項目の選択を扱う仕組みは、QItemSelectionModel クラスによって提供されます。すべての標準ビューは、デフォルトで独自の選択モデルを構築し、通常の方法でそれらとやり取りします。 ビューが使用している選択モデルは、selectionModel() 関数を通じて取得でき、setSelectionModel() を使用して代替の選択モデルを指定できます。ビューが使用する選択モデルを制御できる機能は、同じモデルデータに対して複数の整合性のあるビューを提供したい場合に役立ちます。

一般的に、モデルやビューをサブクラス化していない限り、選択の内容を直接操作する必要はありません。ただし、必要に応じて選択モデルへのインターフェースにアクセスすることができ、これについては「アイテムビューでの選択の処理」で詳しく説明されています。

ビュー間での選択情報の共有

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

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

2番目のビューには、1番目のビューの選択モデルが渡されます。これにより、両方のビューが同じ選択モデルに基づいて動作し、データと選択項目の両方が同期された状態を維持します。

同じ選択モデルを持つ2つのテーブルビュー

上記の例では、同じモデルのデータを表示するために、同じタイプのビューが2つ使用されました。しかし、異なるタイプのビューが2つ使用された場合、選択された項目は各ビューで大きく異なって表現される可能性があります。例えば、テーブルビューでの連続した選択範囲が、ツリービューでは断片化されたハイライトされた項目の集合として表現されることがあります。

デリゲートクラス

概念

モデル・ビュー・コントローラ(MVC)パターンとは異なり、モデル/ビュー設計には、ユーザーとの対話を管理するための完全に独立したコンポーネントは含まれていません。 一般に、ビューはモデルデータのユーザーへの提示と、ユーザー入力の処理を担当します。この入力の取得方法に一定の柔軟性を持たせるため、インタラクションはデリゲートによって行われます。これらのコンポーネントは入力機能を提供するとともに、一部のビューにおける個々の項目のレンダリングも担当します。デリゲートを制御するための標準インターフェースは、QAbstractItemDelegate クラスで定義されています。

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

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

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

Qtに付属する標準ビューは、編集機能を提供するためにQStyledItemDelegate のインスタンスを使用しています。このデリゲートインターフェースのデフォルト実装は、各標準ビュー(QListView 、QTableView 、QTreeView )において、アイテムを通常のスタイルでレンダリングします。

すべての標準ロールは、標準ビューで使用されるデフォルトのデリゲートによって処理されます。これらの解釈方法については、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 を押すと、デリゲートは値をモデルにコミットし、スピンボックスを閉じます。 スピンボックスに独自のイベントフィルターを実装することで、この動作を変更し、ニーズに合わせた編集ヒントを提供できます。例えば、closeEditor() を「EditNextItem 」というヒント付きで発火させることで、ビュー内の次の項目の編集を自動的に開始させることができます。

イベントフィルタの使用を必要としない別のアプローチとして、独自のエディタウィジェットを提供する方法があります。便宜上、QSpinBox をサブクラス化するとよいでしょう。 この代替アプローチでは、追加のコードを書く手間はかかりますが、エディタウィジェットの動作をより細かく制御できるようになります。標準のQtエディタウィジェットの動作をカスタマイズする必要がある場合は、通常、デリゲートにイベントフィルタを実装するほうが簡単です。

デリゲートは必ずしもこれらのヒントを発行する必要はありませんが、ヒントを発行しないデリゲートはアプリケーションへの統合度が低くなり、一般的な編集操作をサポートするためのヒントを発行するデリゲートに比べて使い勝手が悪くなります。

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

概念

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

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

選択は、選択範囲で構成されます。これらは、選択された項目の各範囲について、開始および終了のモデルインデックスのみを記録することで、大量の項目の選択に関する情報を効率的に管理します。連続していない項目の選択は、選択範囲を複数使用して選択内容を記述することで構成されます。

選択は、選択モデルが保持するモデルインデックスの集合に適用されます。最後に適用された項目の選択は、「現在の選択」と呼ばれます。この選択の効果は、特定の種類の選択コマンドを使用することで、適用後であっても変更することが可能です。これらについては、このセクションの後半で説明します。

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

ビューには、常に「現在の項目」と「選択された項目」という 2 つの独立した状態が存在します。1 つの項目が、現在の項目であると同時に選択された項目となることもあります。たとえば、キーボードによるナビゲーションには現在の項目が必要となるため、ビューは常に現在の項目が存在するように管理する役割を担っています。

以下の表は、現在の項目と選択項目の違いをまとめたものです。

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

選択を操作する際、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() シグナルをスロットに接続することで、選択状態が変化した際に、モデル内で選択または選択解除された項目を確認できます。このスロットは、2つの `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);

この操作の結果はテーブルビューに表示され、達成した内容を視覚的に確認するのに便利です:

更新された選択モデルの色が反転しています

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

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つのインデックスのみが指定されていますが、「Columns 」および「Rows 」の選択フラグを使用することで、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() 関数を使用して、特定の項目が別のレベルの項目の親であるかどうかを判定します。

新しいモデルの作成

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

QAbstractItemModel クラスは、情報を階層構造で整理するデータソースをサポートできるほど柔軟なインターフェースを提供しており、データの挿入、削除、変更、あるいは何らかの方法でソートされる可能性にも対応しています。また、ドラッグ&ドロップ操作のサポートも提供しています。

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

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

より複雑なモデルの例については、「Simple Tree Model」の例を参照してください。

QAbstractItemModel のサブクラスに関する要件については、「Model Subclassing Reference」ドキュメントでより詳細に説明されています。

モデルの設計

既存のデータ構造に対して新しいモデルを作成する場合、データへのインターフェースを提供するためにどのタイプのモデルを使用すべきかを検討することが重要です。 データがすでに C++ コンテナ、あるいは反復可能な C++ 範囲をモデル化する任意の型に格納されている場合、入力範囲とその要素型がサポートされていれば、QRangeModel を使用することで、サブクラス化を行うことなく適切なモデルを提供できる可能性があります。

既製のモデルが適用できず、データ構造が項目のリストやテーブルとして表現できる場合は、QAbstractListModel またはQAbstractTableModel をサブクラス化することができます。これらのクラスは、多くの関数に対して適切なデフォルトの実装を提供しているからです。

ただし、基になるデータ構造が階層的なツリー構造でしか表現できない場合は、QAbstractItemModel をサブクラス化する必要があります。このアプローチは、「Simple Tree Model」の例で採用されています。

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

基盤となるデータ構造がどのような形式であっても、特殊なモデルでは、標準の `QAbstractItemModel ` API を、基盤となるデータ構造により自然にアクセスできる 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;
};

モデルのコンストラクタを除けば、実装する必要がある関数は2つだけです。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 を返します。

QTreeView やQTableView などの一部のビューでは、項目データとともにヘッダーを表示することができます。モデルがヘッダー付きのビューで表示される場合、ヘッダーには行番号と列番号を表示させたいでしょう。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() 関数を実装することをお勧めします。

1つのアイテムは複数の役割を持つことができ、指定された役割に応じて異なるデータを出力します。このモデルのアイテムは「DisplayRole 」という1つの役割しか持たないため、指定された役割に関係なくアイテムのデータを返します。ただし、「DisplayRole 」向けに提供したデータを、他の役割(例えば、ビューがツールチップでアイテムに関する情報を表示するために使用できる「ToolTipRole 」など)でも再利用することは可能です。

編集可能なモデル

読み取り専用モデルは、ユーザーにシンプルな選択肢を提示する方法を示していますが、多くのアプリケーションでは、編集可能なリストモデルの方がはるかに有用です。 読み取り専用モデルを修正して項目を編集可能にするには、読み取り専用用に実装した data() 関数を変更し、さらにflags() とsetData() の 2 つの関数を実装します。以下の関数宣言をクラス定義に追加します:

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

このモデルでは、モデルのインデックスに対応する文字列リスト内の項目が、指定された値に置き換えられます。ただし、文字列リストを変更する前に、インデックスが有効であること、項目の型が正しいこと、そしてそのロールがサポートされていることを確認する必要があります。 慣例として、標準のアイテムデリゲートで使用されるロールであるEditRole をロールとして指定することを推奨しています。ただし、ブール値の場合はQt::CheckStateRole を使用し、Qt::ItemIsUserCheckable フラグを設定することで、値の編集にチェックボックスを使用することができます。 このモデルにおける基となるデータはすべてのロールで共通であるため、この仕様は単にモデルを標準コンポーネントと統合しやすくするためのものです。

データが設定されると、モデルはビューに対してデータの一部が変更されたことを通知する必要があります。これは、dataChanged() シグナルを発行することで行われます。変更されたデータは 1 項目のみであるため、シグナルで指定される項目の範囲は 1 つのモデルインデックスのみに制限されます。

また、data()関数も変更して、Qt::EditRole のチェックを追加する必要があります:

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()を呼び出して操作を完了し、モデルの次元が変更されたことを他のコンポーネントに通知します。成功した場合はtrueを返します。

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

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サブクラス化リファレンス』ドキュメントでは、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);

リスト内の各項目には、テキストラベルとアイコンを表示できます。テキストの描画に使用される色やフォントを変更することで、項目の外観をカスタマイズできます。ツールチップ、ステータスチップ、および「これは何?」ヘルプはすべて簡単に設定でき、リストがアプリケーションに適切に統合されるようにできます。

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

デフォルトでは、リスト内の項目は作成順に表示されます。Qt::SortOrder で指定された基準に従って項目リストをソートし、アルファベット順(昇順または降順)に並べ替えることができます:

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

ツリーウィジェット

ツリー、つまり項目の階層リストは、QTreeWidget およびQTreeWidgetItem クラスによって提供されます。ツリーウィジェット内の各項目は、独自の子項目を持つことができ、複数の列の情報を表示できます。ツリーウィジェットの作成方法は、他のウィジェットと同様です:

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

テーブルウィジェット

スプレッドシートアプリケーションに見られるような項目のテーブルは、QTableWidget およびQTableWidgetItem を使用して構築されます。これらにより、ヘッダーと項目を備えたスクロール可能なテーブルウィジェットが提供されます。

テーブルは、行と列の数を指定して作成することも、サイズが未設定のテーブルに必要に応じて行や列を追加していくことも可能です。

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

なお、テーブルの行と列は0から始まることに注意してください。

共通機能

各便利クラスに共通する、アイテムベースの機能が数多くあり、これらは各クラスの同じインターフェースを通じて利用可能です。以下のセクションでは、さまざまなウィジェットの例を交えてこれらを紹介します。各関数の使用方法の詳細については、各ウィジェットの「モデル/ビュークラス一覧」を参照してください。

非表示のアイテム

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

この操作はアイテム単位で行われるため、3つの利便性向上のためのクラスすべてで同じ関数が利用可能です。

選択

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

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エンコードされたデータとしてインポートおよびエクスポートできます。

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

ビューからの項目のエクスポートのみを許可し、ビューへのデータのドロップを許可しないモデルに必要な要件は、ドラッグ&ドロップ機能を完全に有効にしたモデルに必要な要件よりも少ないです。

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

便利なビューの使用

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

すべての標準アイテムには、ドラッグ&ドロップ用のフラグが1つまたは両方が設定されていますが、組み込みのドラッグ&ドロップ機能を活用するには、通常、ビュー自体でさまざまなプロパティを設定する必要があります。

  • 項目のドラッグを有効にするには、ビューの `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::ItemIsDragEnabled およびQt::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;
}

また、モデルは、指定された形式でデータをエンコードするためのコードも提供する必要があります。これは、他のドラッグ&ドロップ操作と同様に、QAbstractItemModel::mimeData() 関数を再実装してQMimeData オブジェクトを提供することで実現されます。

以下のコードは、指定されたインデックスのリストに対応する各データ項目が、プレーンテキストとしてエンコードされ、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()関数の実装を提供する必要があります。

プロキシモデル

モデル/ビューフレームワークでは、単一のモデルによって提供されるデータ項目を任意の数のビューで共有することができ、それぞれのビューは同じ情報を全く異なる方法で表現することが可能です。 カスタムビューやデリゲートは、同じデータを根本的に異なる形で表現するための効果的な手段です。しかし、アプリケーションでは、項目のリストに対して異なる並べ替えを施したビューなど、同じデータの処理済みバージョンに対する従来のビューを提供する必要がある場合がよくあります。

ソートやフィルタリングの操作をビューの内部関数として実行するのが適切に見えるかもしれませんが、このアプローチでは、複数のビューが、こうした処理負荷の高い操作の結果を共有することができません。その代替案として、モデル自体内でソートを行うアプローチもありますが、これでは各ビューが、最新の処理操作に従って整理されたデータ項目を表示しなければならないという同様の問題が生じます。

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

プロキシモデルの使用

プロキシモデルは、既存のモデルと任意の数のビューの間に挿入することができます。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 基底クラスで定義されている多くの仮想関数の実装を提供する必要があります。実装が必要な関数の数は、モデルの種類(ビューに単純なリスト、テーブル、またはアイテムの複雑な階層構造のいずれを提供するか)によって異なります。QAbstractListModel およびQAbstractTableModel を継承するモデルは、これらのクラスが提供する関数のデフォルトの実装を利用できます。ツリー状の構造でデータ項目を公開するモデルは、QAbstractItemModel に定義されている多くの仮想関数の実装を提供する必要があります。

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

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

アイテムデータの取り扱い

モデルは、提供するデータに対してさまざまなレベルのアクセス権限を設定できます。単純な読み取り専用コンポーネントである場合もあれば、サイズ変更操作をサポートするモデルや、アイテムの編集を許可するモデルもあります。

読み取り専用アクセス

モデルが提供するデータに対して読み取り専用アクセスを提供するには、モデルのサブクラスで以下の関数を実装する必要があります:

flags()他のコンポーネントが、モデルが提供する各アイテムに関する情報を取得するために使用されます。多くのモデルでは、フラグの組み合わせとしてQt::ItemIsEnabled およびQt::ItemIsSelectable を含める必要があります。
data()ビューやデリゲートにアイテムデータを供給するために使用されます。一般的に、モデルが供給する必要があるのは、Qt::DisplayRole およびアプリケーション固有のユーザーロールに関するデータのみですが、Qt::ToolTipRole 、Qt::AccessibleTextRole 、Qt::AccessibleDescriptionRole に関するデータも提供することが推奨されます。各ロールに関連付けられた型については、Qt::ItemDataRole 列挙型のドキュメントを参照してください。
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()を呼び出す必要があります。

一般的に、これらの関数は操作が成功した場合に true を返す必要があります。ただし、操作が部分的にしか成功しなかった場合、例えば指定された行数未満しか挿入できなかった場合などがあります。そのような場合、モデルは失敗を示すために 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() コンストラクタで生成された無効なモデルインデックスを返さなければなりません。

上記の 2 つの関数は、createIndex() ファクトリ関数を使用して、他のコンポーネントが使用するインデックスを生成します。モデルが、後でモデルインデックスを対応するアイテムに再関連付けできるように、この関数に一意の識別子を渡すのが一般的です。

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

モデル/ビュークラスはドラッグ&ドロップ操作をサポートしており、多くのアプリケーションで十分なデフォルトの挙動を提供します。ただし、ドラッグ&ドロップ操作中の項目のエンコード方法、デフォルトでコピーされるか移動されるか、および既存のモデルへの挿入方法をカスタマイズすることも可能です。

さらに、利便性ビュークラスは、既存の開発者が期待する動作に厳密に従うよう設計された特殊な動作を実装しています。「利便性ビュー」のセクションでは、この動作の概要を説明しています。

MIMEデータ

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

このMIMEタイプを使用してエンコードされたデータは、シリアル化対象のアイテムを含むQModelIndexList を引数としてQAbstractItemModel::mimeData()を呼び出すことで取得できます。

カスタムモデルでドラッグ&ドロップ機能を実装する際、以下の関数を再実装することで、データを特定の形式でエクスポートすることが可能です:

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

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

多くのモデルにおいて、text/plain やimage/png といった MIME タイプで表される一般的な形式で項目のコンテンツを提供することは有用です。なお、画像、色、HTML ドキュメントは、QMimeData::setImageData()、QMimeData::setColorData()、およびQMimeData::setHtml() 関数を使用して、QMimeData オブジェクトに簡単に追加できることに注意してください。

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

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

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

組み込みのMIMEタイプに対してQAbstractItemModel のデフォルト実装を活用するには、新しいモデルでは以下の関数の再実装を提供する必要があります:

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

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

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

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

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

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

便利ビュー

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

大量のデータに対するパフォーマンスの最適化

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

もう1つの例として、動的にデータが設定されるツリーモデルが挙げられます。ここでは、ツリーモデル内のブランチが展開された際に、fetchMore() を再実装します。

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

モデル/ビュークラス

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

QAbstractItemDelegate

モデルからのデータ項目の表示および編集に使用されます

QAbstractItemModel

アイテムモデルクラスのための抽象インターフェース

QAbstractItemView

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

QAbstractListModel

1次元リストモデルを作成するためにサブクラス化可能な抽象モデル

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

データモデル内のデータを特定するために使用される

QRangeModel

任意の C++ 範囲に対して QAbstractItemModel を実装します

QRangeModel::ItemAccess

テンプレートは、QRangeModelが個々の項目のロールデータにアクセスする方法を制御するためのカスタマイズポイントを提供する

QRangeModel::RowOptions

テンプレートは、QRangeModelが行として使用される型をどのように表現するかを制御するためのカスタマイズポイントを提供します

QRangeModelAdapter

任意の C++ 範囲に対する QAbstractItemModel 準拠のアクセス

QRangeModelAdapter::ColumnIterator

モデル行の列に対して、STL スタイルの非 const イテレータを提供します

QRangeModelAdapter::ConstColumnIterator

モデル行の列に対して、STL スタイルの非 const イテレータを提供します

QRangeModelAdapter::ConstRowIterator

モデルの行に対して、STL スタイルの const イテレータを提供します

QRangeModelAdapter::ConstRowReference

QRangeModel 内の const 行をラップする参照ラッパー

QRangeModelAdapter::DataReference

QRangeModel内の項目をラップする参照ラッパー

QRangeModelAdapter::RowIterator

モデルの行を巡回する STL スタイルの非 const イテレータを提供します

QRangeModelAdapter::RowReference

QRangeModel内の行に対する参照ラッパー

QRangeModelAdapter::RowReferenceBase

RowReference および ConstRowReference 用の共通 API

QSortFilterProxyModel

別のモデルとビューの間でやり取りされるデータのソートおよびフィルタリングのサポート

QStandardItem

QStandardItemModel クラスで使用するためのアイテム

QStandardItemEditorCreator

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

QStandardItemModel

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

QStringListModel

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

QStyledItemDelegate

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

QTableView

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

QTableWidget

デフォルトモデルを備えたアイテムベースのテーブルビュー

QTableWidgetItem

QTableWidget クラスで使用するためのアイテム

QTableWidgetSelectionRange

モデルインデックスや選択モデルを使用せずに、モデル内の選択とやり取りを行う方法

QTreeView

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

QTreeWidget

事前定義されたツリーモデルを使用するツリービュー

QTreeWidgetItem

QTreeWidget 便利クラスで使用するためのアイテム

QTreeWidgetItemIterator

QTreeWidget インスタンス内の項目を反復処理する方法

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.