このページでは

モデル/ビューのチュートリアル

すべてのUI開発者はモデル・ビュープログラミングについて知っておくべきであり、このチュートリアルの目的は、このトピックについて分かりやすく解説することです。

テーブル、リスト、ツリーウィジェットは、GUIで頻繁に使用されるコンポーネントです。これらのウィジェットがデータにアクセスするには、2つの異なる方法があります。従来の方法では、ウィジェットがデータを格納するための内部コンテナを備えています。このアプローチは非常に直感的ですが、多くの複雑なアプリケーションでは、データの同期問題を引き起こす原因となります。 2つ目のアプローチがモデル・ビュープログラミングです。この方式では、ウィジェットは内部のデータコンテナを保持しません。標準化されたインターフェースを通じて外部データにアクセスするため、データの重複を回避できます。これは一見複雑に見えるかもしれませんが、詳しく見てみると理解しやすいだけでなく、モデル・ビュープログラミングの多くの利点も明らかになります。

ツリービューに表示されるディレクトリとその内容

その過程で、Qtが提供する以下のような基本的な技術について学びます:

  • 標準ウィジェットとモデル/ビューウィジェットの違い
  • フォームとモデル間のアダプタ
  • 簡単なモデル/ビューアプリケーションの開発
  • 定義済みのモデル
  • 中級レベルのトピック(例:
    • ツリービュー
    • 選択
    • デリゲート
    • モデルテストを用いたデバッグ

また、新しいアプリケーションをモデル・ビュープログラミングでより簡単に記述できるのか、それとも従来のウィジェットでも同様に機能するのかについても理解できるようになります。

このチュートリアルには、編集して自分のプロジェクトに組み込めるサンプルコードが含まれています。チュートリアルのソースコードは、Qtのexamples/widgets/tutorials/modelviewディレクトリにあります。

さらに詳しい情報については、リファレンスドキュメントも参照してください。

1. はじめに

Model/Viewは、データセットを扱うウィジェットにおいて、データとビューを分離するために使用される技術です。標準のウィジェットはデータとビューを分離するようには設計されていないため、Qtには2種類の異なるウィジェットが存在します。どちらのタイプのウィジェットも見た目は同じですが、データとのやり取りの仕方は異なります。

標準ウィジェットは、ウィジェットの一部であるデータを使用します。

「ビュー」と「データ」が分離された構造

ビュークラスは、外部のデータ(モデル)を操作します。

データに対して「ビュー」と「モデル」を用いる構造

1.1 標準ウィジェット

標準のテーブルウィジェットについて詳しく見てみましょう。テーブルウィジェットは、ユーザーが変更できるデータ要素の2次元配列です。テーブルウィジェットが提供するデータ要素を読み書きすることで、プログラムのフローに組み込むことができます。 この方法は非常に直感的で、多くのアプリケーションで有用ですが、標準のテーブルウィジェットを使用してデータベーステーブルを表示・編集するには問題が生じることがあります。データの2つのコピー(ウィジェットの外側にあるものと、ウィジェットの内側にあるもの)を連携させる必要があるためです。開発者は、これら2つのバージョンを同期させる責任を負います。さらに、表示とデータが密接に結合しているため、単体テストの作成が難しくなります。

1.2 モデル/ビューの登場

モデル/ビューは、より汎用性の高いアーキテクチャを用いた解決策を提供するために登場しました。モデル/ビューは、標準ウィジェットで発生しうるデータの一貫性に関する問題を解消します。また、1つのモデルを複数のビューに渡すことができるため、同じデータに対して複数のビューを使用することも容易になります。 最も重要な違いは、モデル/ビューのウィジェットがテーブルセルの背後でデータを保持しないという点です。実際、これらのウィジェットはデータから直接操作を行います。ビュークラスはデータの構造を認識しないため、データをQAbstractItemModel インターフェースに準拠させるためのラッパーを用意する必要があります。ビューはこのインターフェースを使用して、データからの読み取りや書き込みを行います。QAbstractItemModel を実装するクラスのインスタンスは、すべてモデルと呼ばれます。ビューがモデルへのポインタを受け取ると、その内容を読み込んで表示し、エディタとして機能します。

1.3 モデル/ビュー・ウィジェットの概要

以下に、モデル/ビューウィジェットと、それに対応する標準ウィジェットの概要を示します。

ウィジェット標準ウィジェット
(アイテムベースの利便性クラス)
モデル/ビューのビュークラス
(外部データとの連携用)
内容をリスト形式で表示するディレクトリQListWidgetQListView
内容を表形式で表示するディレクトリQTableWidgetQTableView
内容をツリー形式で表示するディレクトリQTreeWidgetQTreeView
内容を列形式で表示するディレクトリQColumnView ツリーをリストの階層として表示します
モデル内のエントリを表示するコンボボックスQComboBox ビュークラスとしても、従来のウィジェットとしても機能する

1.4 フォームとモデルの間でアダプタを使用する

フォームとモデルの間にアダプタを配置しておくと便利です。

テーブルに格納されたデータはテーブル自体から直接編集することもできますが、テキストフィールドでデータを編集するほうがはるかに快適です。 データセットではなく単一の値を操作するウィジェット(QLineEdit 、QCheckBox など)については、データとビューを分離する直接的なモデル/ビューの対応関係が存在しないため、フォームをデータソースに接続するにはアダプタが必要となります。

QDataWidgetMapper は、フォームウィジェットをテーブルの行にマッピングし、データベーステーブル用のフォームを非常に簡単に構築できるため、優れたソリューションです。

個人情報およびその他の情報を記載するフォーム

アダプタのもう1つの例として、QCompleter が挙げられます。Qtには、QComboBox や、以下に示すようにQLineEdit といったQt Widgetsでオートコンプリート機能を提供するためのQCompleter があります。QCompleter は、データソースとしてモデルを使用しています。

「Em」という文字に対する提案を追加する

2. 簡単なモデル/ビューアプリケーション

モデル/ビューアプリケーションを開発したい場合、どこから始めればよいでしょうか? まずは簡単な例から始めて、段階的に機能を拡張していくことをお勧めします。そうすることで、アーキテクチャの理解が格段に容易になります。 IDEを起動する前にモデル/ビューのアーキテクチャを詳細に理解しようとすると、多くの開発者にとってあまり効率的ではないことが分かっています。デモデータが含まれたシンプルなモデル/ビューアプリケーションから始める方が、はるかに簡単です。ぜひ試してみてください!以下の例のデータを、ご自身のデータに置き換えるだけでOKです。

以下に、モデル/ビュープログラミングのさまざまな側面を示す、非常にシンプルで独立した7つのアプリケーションを掲載します。ソースコードは、examples/widgets/tutorials/modelview ディレクトリ内にあります。

2.1 読み取り専用テーブル

まずは、QTableView を使用してデータを表示するアプリケーションから始めます。編集機能については、後で追加していきます。

(ソースファイル: examples/widgets/tutorials/modelview/1_readonly/main.cpp)

// main.cpp
#include <QApplication>
#include <QTableView>
#include "mymodel.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QTableView tableView;
    MyModel myModel;
    tableView.setModel(&myModel);
    tableView.show();
    return a.exec();
}

おなじみのmain()関数があります:

ここで興味深い点があります。MyModelのインスタンスを作成し、tableView.setModel(&myModel); を使用して、そのポインタをtableView に渡しています。tableView は、受け取ったポインタのメソッドを呼び出し、次の2つの情報を取得します:

  • 表示すべき行数と列数。
  • 各セルにどのコンテンツを出力すべきか。

モデル側では、これに対応するためのコードが必要です。

テーブル形式のデータセットがあるので、より汎用的な `QAbstractItemModel` よりも使い勝手の良い `QAbstractTableModel ` から始めましょう。

(ファイル出典: examples/widgets/tutorials/modelview/1_readonly/mymodel.h)

// mymodel.h
#include <QAbstractTableModel>

class MyModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    explicit MyModel(QObject *parent = nullptr);

    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
};

QAbstractTableModel 3つの抽象メソッドの実装が必要です。

(ファイルソース: examples/widgets/tutorials/modelview/1_readonly/mymodel.cpp)

// mymodel.cpp
#include "mymodel.h"

MyModel::MyModel(QObject *parent)
    : QAbstractTableModel(parent)
{
}

int MyModel::rowCount(const QModelIndex & /*parent*/) const
{
   return 2;
}

int MyModel::columnCount(const QModelIndex & /*parent*/) const
{
    return 3;
}

QVariant MyModel::data(const QModelIndex &index, int role) const
{
    if (role == Qt::DisplayRole)
       return QString("Row%1, Column%2")
                   .arg(index.row() + 1)
                   .arg(index.column() +1);

    return QVariant();
}

行数と列数は、MyModel::rowCount() およびMyModel::columnCount() によって提供されます。ビューがセルのテキストを把握する必要がある場合、MyModel::data() メソッドを呼び出します。行と列の情報はパラメータindex で指定され、役割はQt::DisplayRole に設定されます。その他の役割については次のセクションで説明します。 この例では、表示すべきデータが生成されています。実際のアプリケーションでは、MyModel にはMyData というメンバーがあり、すべての読み取りおよび書き込み操作のターゲットとして機能します。

この簡単な例は、モデルの受動的な性質を示しています。モデルは、いつ使用されるか、あるいはどのデータが必要かを知りません。単に、ビューから要求があるたびにデータを提供するだけです。

モデルのデータを変更する必要がある場合はどうなるでしょうか?ビューは、データが変更され、再度読み込む必要があることをどのように認識するのでしょうか?モデルは、どの範囲のセルが変更されたかを示すシグナルを発信する必要があります。これについては、2.3節で説明します。

2.2 ロールによる読み取り専用例の拡張

モデルは、ビューに表示されるテキストの内容を制御するだけでなく、その外観も制御します。モデルを少し変更すると、次のような結果になります:

セルにカスタム書式が適用された表

実際、フォント、背景色、配置、チェックボックスを設定するために変更が必要なのは、data() メソッドだけである。以下に、上記の結果を生成するdata() メソッドを示す。違いは、今回はパラメータ int role を使用し、その値に応じて異なる情報を返す点にある。

(ソースファイル: examples/widgets/tutorials/modelview/2_formatting/mymodel.cpp)

// mymodel.cpp
QVariant MyModel::data(constQModelIndex&index, introle)const
{
    introw=index.row();
    intcol=index.column();
    // このメソッドが呼び出された際にログメッセージを出力する
    qDebug() << QString("row %1, col%2, role %3")
           .arg(row).arg(col).arg(role);

    switch(role) {
    caseQt::DisplayRole:
        if(row== 0&&col== 1)returnQString("<--left");
        if(row== 1&&col== 1)returnQString("right-->");

        returnQString("行%1, 列%2")
                .arg(row+ 1)
                .arg(col+ 1);
    caseQt::FontRole:
        if(row== 0&&col== 0) {// セル(0,0)のみフォントを変更
            QFont boldFont;
            boldFont.setBold(true);
            returnboldFont;
        }
        break;
    caseQt::BackgroundRole:
        if(row== 1&&col== 2)  // セル(1,2)のみ背景色を変更
            returnQBrush(Qt::red);
        break;
    caseQt::TextAlignmentRole:
        if(row== 1&&col== 1)// セル(1,1)のみテキストの配置を変更
            return int(Qt::AlignRight|Qt::AlignVCenter);
        break;
    caseQt::CheckStateRole:
        if(row== 1&&col== 0)// セル(1,0)にチェックボックスを追加
            returnQt::Checked;
        break;
    }
    returnQVariant();
}

各書式設定プロパティは、data() メソッドを個別に呼び出すことでモデルから取得されます。role パラメータは、どのプロパティが要求されているかをモデルに通知するために使用されます:

enum Qt::ItemDataRole意味型
Qt::DisplayRoletextQString
Qt::FontRolefontQFont
BackgroundRoleセルの背景用のブラシQBrush
Qt::TextAlignmentRoleテキストの配置enum Qt::AlignmentFlag
Qt::CheckStateRoleQVariant() でチェックボックスを非表示にし、

でチェックボックスを設定しますQt::Checked

またはQt::Unchecked

enum Qt::ItemDataRole

Qt::ItemDataRole 列挙型の機能について詳しくは、Qt名前空間のドキュメントを参照してください。

次に、モデルを分離したことがアプリケーションのパフォーマンスにどのような影響を与えるかを確認する必要があります。そこで、ビューが `data()` メソッドをどのくらいの頻度で呼び出しているかをトレースしてみましょう。ビューがモデルをどのくらいの頻度で呼び出しているかを追跡するために、`data()` メソッド内にデバッグ文を挿入し、エラー出力ストリームにログを出力するようにしました。 この小さな例では、data()は42回呼び出されます。フィールドにカーソルを合わせるたびに、data()が再度呼び出され、各セルにつき7回ずつ実行されます。そのため、data()が呼び出された際にデータが利用可能であり、負荷の高い検索操作がキャッシュされていることを確認することが重要です。

2.3 テーブルセル内の時計

セルに時刻を表示する表

ここでも読み取り専用のテーブルを使用していますが、今回は現在の時刻を表示しているため、コンテンツが1秒ごとに更新されます。

(ソースファイル: examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)

QVariant MyModel::data(const QModelIndex &index, int role) const
{
    int row = index.row();
    int col = index.column();

    if (role == Qt::DisplayRole && row == 0 && col == 0)
        return QTime::currentTime().toString();

    return QVariant();
}

時計を刻むために何かが欠けています。1秒ごとに、時刻が変更されたこと、そして再度読み込む必要があることをビューに通知する必要があります。これにはタイマーを使用します。コンストラクタ内で、その間隔を1秒に設定し、タイムアウト信号を接続します。

(ファイルソース: examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)

MyModel::MyModel(QObject *parent)
    : QAbstractTableModel(parent)
    , timer(new QTimer(this))
{
    timer->setInterval(1000);
    connect(timer, &QTimer::timeout , this, &MyModel::timerHit);
    timer->start();
}

対応するスロットは以下の通りです:

(ソースファイル: examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)

void MyModel::timerHit()
{
    // we identify the top left cell
    QModelIndex topLeft = createIndex(0,0);
    // emit a signal to make the view reread identified data
    emit dataChanged(topLeft, topLeft, {Qt::DisplayRole});
}

dataChanged()シグナルを発信することで、ビューに左上のセルのデータを再度読み込むよう要求します。なお、dataChanged()シグナルをビューに明示的に接続していない点に注意してください。これは、setModel()を呼び出した際に自動的に行われました。

2.4 列および行用のヘッダーの設定

ヘッダーは、ビューのメソッドを使用して非表示にすることができます:tableView->verticalHeader()->hide();

列見出しを示す表

ただし、ヘッダーの内容はモデルを介して設定されるため、headerData() メソッドを再実装します:

(ファイルソース: examples/widgets/tutorials/modelview/4_headers/mymodel.cpp)

QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
        switch (section) {
        case 0:
            return QString("first");
        case 1:
            return QString("second");
        case 2:
            return QString("third");
        }
    }
    return QVariant();
}

なお、headerData() メソッドには、MyModel::data() と同じ意味を持つパラメータ role も含まれていることに注意してください。

2.5 最小限の編集の例

この例では、表のセルに入力された値を繰り返し使用して、ウィンドウのタイトルに自動的に内容を設定するアプリケーションを作成します。ウィンドウのタイトルに簡単にアクセスできるようにするため、QTableView をQMainWindow 内に配置します。

編集機能の利用可否はモデルによって決定されます。利用可能な編集機能を有効にするには、モデルを変更するだけで済みます。これを行うには、setData() およびflags() という仮想メソッドを再実装します。

(ソースファイル: examples/widgets/tutorials/modelview/5_edit/mymodel.h)

// mymodel.h
#include <QAbstractTableModel>
#include <QString>

const int COLS= 3;
const int ROWS= 2;

class MyModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    MyModel(QObject *parent = nullptr);
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
    Qt::ItemFlags flags(const QModelIndex &index) const override;
private:
    QString m_gridData[ROWS][COLS];  //holds text entered into QTableView
signals:
    void editCompleted(const QString &);
};

データの格納には、the (2次元配列)QString 、m_gridData を使用します。これにより、m_gridData がMyModel の中核となります。MyModel の残りの部分はラッパーとして機能し、m_gridData をQAbstractItemModel インターフェースに適応させます。また、editCompleted() シグナルを導入しており、これにより変更されたテキストをウィンドウのタイトルに転送することが可能になります。

(ソースファイル: examples/widgets/tutorials/modelview/5_edit/mymodel.cpp)

bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (role == Qt::EditRole) {
        if (!checkIndex(index))
            return false;
        //save value from editor to member m_gridData
        m_gridData[index.row()][index.column()] = value.toString();
        //for presentation purposes only: build and emit a joined string
        QString result;
        for (int row = 0; row < ROWS; row++) {
            for (int col= 0; col < COLS; col++)
                result += m_gridData[row][col] + ' ';
        }
        emit editCompleted(result);
        return true;
    }
    return false;
}

setData()は、ユーザーがセルを編集するたびに呼び出されます。index パラメータは、どのフィールドが編集されたかを示し、value は編集処理の結果を提供します。 セルにはテキストのみが含まれているため、roleは常にQt::EditRole に設定されます。チェックボックスが存在し、ユーザーの権限設定でチェックボックスの選択が許可されている場合は、roleがQt::CheckStateRole に設定された状態で呼び出しが行われることもあります。

(ファイルソース: examples/widgets/tutorials/modelview/5_edit/mymodel.cpp)

Qt::ItemFlags MyModel::flags(const QModelIndex &index) const
{
    return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}

セルのさまざまなプロパティは、flags() を使用して調整できます。

Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled を返せば、そのセルが選択可能であることをエディタに通知できます。

あるセルを編集した際に、その特定のセル内のデータ以外のデータも変更される場合、変更されたデータが読み取られるようにするには、モデルが `dataChanged()` シグナルを発行する必要があります。

3. 中級トピック

3.1 TreeView

上記の例を、ツリービューを備えたアプリケーションに変換することができます。QTableView をQTreeView に置き換えるだけで、読み書き可能なツリーが実現されます。モデルに変更を加える必要はありません。モデル自体に階層構造がないため、ツリーにも階層構造は存在しません。

階層構造のないテーブルデータを表示するツリービュー

QListView、QTableView およびQTreeView はすべて、リスト、テーブル、ツリーを統合したモデル抽象化を使用しています。これにより、同じモデルから複数の異なるタイプのビュークラスを使用することが可能になります。

リストモデル、テーブルモデル、ツリーモデルを比較した図

現時点でのサンプルモデルの構造は以下の通りです:

現行モデルの構造図

実際のツリーを表示したいと考えています。上記の例では、モデルを作成するためにデータをラップしていました。今回は、階層型データのコンテナであり、QAbstractItemModel も実装しているQStandardItemModel を使用します。ツリーを表示するには、QStandardItemModel にQStandardItemを設定する必要があります。 は、テキスト、フォント、チェックボックス、ブラシなど、アイテムの標準的なプロパティをすべて保持することができます。

モデルとビューの図

(ソースファイル: examples/widgets/tutorials/modelview/6_treeview/mainwindow.cpp)

// modelview.cpp
#include "mainwindow.h"

#include <QTreeView>
#include <QStandardItemModel>
#include <QStandardItem>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , treeView(new QTreeView(this))
    , standardModel(new QStandardItemModel(this))
{
    setCentralWidget(treeView);

    QList<QStandardItem *> preparedRow = prepareRow("first", "second", "third");
    QStandardItem *item = standardModel->invisibleRootItem();
    // adding a row to the invisible root item produces a root element
    item->appendRow(preparedRow);

    QList<QStandardItem *> secondRow = prepareRow("111", "222", "333");
    // adding a row to an item starts a subtree
    preparedRow.first()->appendRow(secondRow);

    treeView->setModel(standardModel);
    treeView->expandAll();
}

QList<QStandardItem *> MainWindow::prepareRow(const QString &first,
                                              const QString &second,
                                              const QString &third) const
{
    return {new QStandardItem(first),
            new QStandardItem(second),
            new QStandardItem(third)};
}

QStandardItemModel をインスタンス化し、コンストラクタにQStandardItems をいくつか追加するだけです。QStandardItem は他のQStandardItems を保持できるため、階層的なデータ構造を作成できます。ノードはビュー内で折りたたんだり展開したりできます。

3.2 選択操作

選択された項目のコンテンツにアクセスし、階層レベルとともにウィンドウのタイトルに出力したいと考えています。

選択したコンテンツが青色で表示されたモデルの図

それでは、いくつかの項目を作成してみましょう:

(ソースファイル: examples/widgets/tutorials/modelview/7_selections/mainwindow.cpp)

#include "mainwindow.h"

#include <QTreeView>
#include <QStandardItemModel>
#include <QItemSelectionModel>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , treeView(new QTreeView(this))
    , standardModel(new QStandardItemModel(this))
{
    setCentralWidget(treeView);
    auto *rootNode = standardModel->invisibleRootItem();

    // defining a couple of items
    auto *americaItem = new QStandardItem("America");
    auto *mexicoItem =  new QStandardItem("Canada");
    auto *usaItem =     new QStandardItem("USA");
    auto *bostonItem =  new QStandardItem("Boston");
    auto *europeItem =  new QStandardItem("Europe");
    auto *italyItem =   new QStandardItem("Italy");
    auto *romeItem =    new QStandardItem("Rome");
    auto *veronaItem =  new QStandardItem("Verona");

    // building up the hierarchy
    rootNode->    appendRow(americaItem);
    rootNode->    appendRow(europeItem);
    americaItem-> appendRow(mexicoItem);
    americaItem-> appendRow(usaItem);
    usaItem->     appendRow(bostonItem);
    europeItem->  appendRow(italyItem);
    italyItem->   appendRow(romeItem);
    italyItem->   appendRow(veronaItem);

    // register the model
    treeView->setModel(standardModel);
    treeView->expandAll();

    // selection changes shall trigger a slot
    QItemSelectionModel *selectionModel = treeView->selectionModel();
    connect(selectionModel, &QItemSelectionModel::selectionChanged,
            this, &MainWindow::selectionChangedSlot);
}

ビューは、独立した選択モデル内で選択を管理しており、これはselectionModel()メソッドで取得できます。スロットをselectionChanged()シグナルに接続するために、この選択モデルを取得します。

(ファイルソース: examples/widgets/tutorials/modelview/7_selections/mainwindow.cpp)

void MainWindow::selectionChangedSlot(const QItemSelection & /*newSelection*/, const QItemSelection & /*oldSelection*/)
{
    // get the text of the selected item
    const QModelIndex index = treeView->selectionModel()->currentIndex();
    QString selectedText = index.data(Qt::DisplayRole).toString();
    // find out the hierarchy level of the selected item
    int hierarchyLevel = 1;
    QModelIndex seekRoot = index;
    while (seekRoot.parent().isValid()) {
        seekRoot = seekRoot.parent();
        hierarchyLevel++;
    }
    QString showString = QString("%1, Level %2").arg(selectedText)
                         .arg(hierarchyLevel);
    setWindowTitle(showString);
}

treeView->selectionModel()->currentIndex() を呼び出して選択に対応するモデルインデックスを取得し、そのモデルインデックスを使用してフィールドの文字列を取得します。 その後、項目のhierarchyLevel を計算するだけです。最上位の項目には親が存在せず、parent()メソッドはデフォルトコンストラクタで生成されたQModelIndex() を返します。このため、parent()メソッドを使用して、反復処理中のステップ数をカウントしながら最上位まで反復処理を行います。

選択モデル(上記参照)は取得できるだけでなく、QAbstractItemView::setSelectionModel で設定することも可能です。選択モデルのインスタンスは1つしか使用されないため、これにより3つのビュークラス間で選択内容を同期させることが可能になります。 3つのビュー間で選択モデルを共有するには、selectionModel() を使用し、その結果をsetSelectionModel() を使用して2番目と3番目のビュークラスに割り当てます。

3.3 定義済みモデル

モデル/ビューを使用する一般的な方法は、特定のデータをラップしてビュークラスで利用できるようにすることです。しかし、Qtでは、一般的な基盤となるデータ構造に対して事前定義されたモデルも提供されています。利用可能なデータ構造のいずれかがアプリケーションに適している場合、事前定義されたモデルは良い選択肢となります。

QRangeModel既存のC++コンテナまたは範囲を適応させます
QStringListModel文字列のリストを格納する
QStandardItemModel任意の階層的項目を格納する
QFileSystemModelローカルファイルシステムをカプセル化する
QSqlQueryModelSQLの結果セットをカプセル化する
QSqlTableModelSQLテーブルをカプセル化する
QSqlRelationalTableModel外部キーを持つSQLテーブルをカプセル化する
QSortFilterProxyModel別のモデルをソートおよび/またはフィルタリングする

3.4 デリゲート

これまでのすべての例では、データはセル内のテキストまたはチェックボックスとして表示され、テキストまたはチェックボックスとして編集されています。これらの表示および編集機能を提供するコンポーネントは、デリゲートと呼ばれます。ビューではデフォルトのデリゲートが使用されているため、デリゲートの扱いはまだ始まったばかりです。 しかし、別のエディタ(スライダーやドロップダウンリストなど)を使いたい場合や、データをグラフとして表示したい場合を想像してみてください。ここでは、星を使って評価を表示する「Star Delegate」という例を見てみましょう。

楽曲とその星評価を一覧にした表

このビューには、setItemDelegate() メソッドがあり、これによってデフォルトのデリゲートが置き換えられ、カスタムデリゲートが設定されます。新しいデリゲートは、QStyledItemDelegate を継承したクラスを作成することで実装できます。星を表示し、入力機能を持たないデリゲートを作成するには、2つのメソッドをオーバーライドするだけで済みます。

class StarDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    StarDelegate(QWidget *parent = nullptr);
    void paint(QPainter *painter, const QStyleOptionViewItem &option,
               const QModelIndex &index) const;
    QSize sizeHint(const QStyleOptionViewItem &option,
                   const QModelIndex &index) const;
};

paint() メソッドは、基になるデータの内容に応じて星を描画します。データはindex.data() を呼び出すことで取得できます。デリゲートのsizeHint() メソッドは、各星の寸法を取得するために使用されるため、セルは星を収容するのに十分な高さと幅を確保します。

ビュークラスのグリッド内で、データを独自のグラフィカルな表現で表示したい場合は、カスタムデリゲートを作成するのが適切な選択です。グリッドの外で表示したい場合は、カスタムデリゲートではなく、カスタムビュークラスを使用します。

Qtドキュメント内のデリゲートに関するその他の参照:

3.5 ModelTest を使用したデバッグ

モデルの受動的な性質は、プログラマーに新たな課題をもたらします。モデル内の不整合は、アプリケーションのクラッシュを引き起こす可能性があります。モデルはビューからの多数の呼び出しを受けるため、どの呼び出しがアプリケーションをクラッシュさせたのか、どの操作が問題を引き起こしたのかを特定するのは困難です。

Qt Labsは、プログラミングの実行中にモデルをチェックする「ModelTest」というソフトウェアを提供しています。モデルが変更されるたびに、ModelTestはモデルをスキャンし、アサートによってエラーを報告します。これは特にツリーモデルにおいて重要です。その階層的な性質上、微妙な不整合が生じる可能性が数多くあるためです。

ビュークラスとは異なり、ModelTestは範囲外のインデックスを使用してモデルをテストします。つまり、ModelTestを使用しない場合は問題なく動作するアプリケーションでも、ModelTestを使用するとクラッシュする可能性があります。そのため、ModelTestを使用する際は、範囲外のすべてのインデックスに対処する必要があります。

例

Qt には、モデル/ビューに関する複数のサンプルが付属しています。これらは「Item Views Examples」ページで確認できます。

© 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.