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

すべてのUI開発者はModelViewプログラミングについて知っている必要があり、このチュートリアルの目的は、このトピックについて分かりやすく紹介することです。

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

その過程で、Qtが提供する基本的な技術についても学んでいきます:

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

また、新しいアプリケーションがモデル/ビュープログラミングでより簡単に書けるのか、それとも古典的なウィジェットでも同じように動作するのかについても学びます。

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

より詳細な情報については、リファレンス・ドキュメントを参照してください。

1.はじめに

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

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

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

1.1 標準ウィジェット

標準のテーブル・ウィジェットを詳しく見てみましょう。テーブル・ウィジェットは、ユーザが変更できるデータ要素の 2D 配列です。テーブル・ウィジェットは、テーブル・ウィジェットが提供するデータ要素を読み書きすることで、プログラム・フローに組み込むことができます。この方法は非常に直感的で多くのアプリケーションで有用ですが、標準的なテーブル・ウィジェットでデータベース・テーブルを表示したり編集したりするには問題があります。1つはウィジェットの外側、もう1つはウィジェットの内側です。開発者は、両方のバージョンを同期させる責任があります。これに加えて、プレゼンテーションとデータの緊密な結合は、ユニットテストを書くことを難しくします。

1.2 モデル/ビューによる救済

モデル/ビューは、より汎用的なアーキテクチャを使用するソリューションを提供するために立ち上がった。Model/viewは、標準的なウィジェットで起こりうるデータの一貫性の問題を排除します。また、モデル/ビューは、1つのモデルを多くのビューに渡すことができるため、同じデータについて複数のビューを使用することが容易になります。最も重要な違いは、モデル/ビューウィジェットはテーブルセルの後ろにデータを保存しないことです。実際、ウィジェットはデータから直接操作します。ビュークラスはデータの構造を知らないので、データをQAbstractItemModel インターフェースに適合させるラッパーを提供する必要があります。ビューはこのインターフェイスを使用して、データからの読み込みとデータへの書き込みを行います。QAbstractItemModel を実装したクラスのインスタンスは、モデルであると言われます。ビューはモデルへのポインタを受け取ると、その内容を読み込んで表示し、そのエディタとなります。

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

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

ウィジェット標準ウィジェット
(アイテムベースの便利なクラス)
モデル/ビュービュークラス
(外部データで使うための)
QListWidgetQListView
QTableWidgetQTableView
QTreeWidgetQTreeView
QColumnView ツリーをリストの階層として表示する
QComboBox ビュークラスとしても従来のウィジェットとしても動作します。

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

フォームとモデルの間にアダプタがあると便利です。

テーブルに保存されたデータをテーブル自身から直接編集することもできますが、テキストフィールドのデータを編集するほうがずっと快適です。データセットの代わりに 1 つの値 (QLineEdit,QCheckBox...) を操作するウィジェットには、データとビューを分離する直接的なモデルとビューの対応関係がありません。したがって、フォームをデータソースに接続するためのアダプタが必要です。

QDataWidgetMapper フォームのウィジェットをテーブルの行にマップし、データベースのテーブル用のフォームをとても簡単に作成できるからです。

アダプタのもうひとつの例はQCompleter です。Qt には、QComboBox のような Qt ウィジェットでオートコンプリートを提供するためのQCompleter や、以下に示すようにQLineEdit があります。QCompleter は、データソースとしてモデルを使用します。

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

モデル/ビュー・アプリケーションを開発したい場合、何から始めればよいでしょうか?簡単な例から始めて、段階的に拡張していくことをお勧めします。そうすることで、アーキテクチャを理解しやすくなります。IDEを起動する前にモデル/ビューのアーキテクチャを詳細に理解しようとすることは、多くの開発者にとって都合が悪いことが証明されています。デモデータを持つ単純なモデル/ビュー・アプリケーションから始める方がはるかに簡単です。試してみてください!以下の例のデータを、あなた自身のデータに置き換えるだけです。

以下は、モデル/ビュー・プログラミングのさまざまな側面を示す、非常にシンプルで独立した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 に設定されます。その他のロールは次のセクションで説明します。この例では、表示すべきデータが生成されます。実際のアプリケーションでは、MyModelMyData というメンバを持ち、このメンバがすべての読み書き操作のターゲットとなります。

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

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

2.2 役割による読み取り専用の例の拡張

ビューが表示するテキストを制御することに加えて、モデルはテキストの外観も制御します。モデルを少し変更すると、次のような結果が得られます:

実際には、data ()メソッド以外は、フォント、背景色、整列、チェックボックスを設定するために変更する必要はありません。以下は、上記の結果を生成するdata ()メソッドです。違いは、今回はパラメータint roleを使い、その値によって異なる情報を返すようにしている点です。

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

// mymodel.cpp
QVariant MyModel::data(const QModelIndex &index, int role) const
{
    int row = index.row();
    int col = index.column();
    // generate a log message when this method gets called
    qDebug() << QString("row %1, col%2, role %3")
            .arg(row).arg(col).arg(role);

    switch (role) {
    case Qt::DisplayRole:
        if (row == 0 && col == 1) return QString("<--left");
        if (row == 1 && col == 1) return QString("right-->");

        return QString("Row%1, Column%2")
                .arg(row + 1)
                .arg(col +1);
    case Qt::FontRole:
        if (row == 0 && col == 0) { // change font only for cell(0,0)
            QFont boldFont;
            boldFont.setBold(true);
            return boldFont;
        }
        break;
    case Qt::BackgroundRole:
        if (row == 1 && col == 2)  // change background only for cell(1,2)
            return QBrush(Qt::red);
        break;
    case Qt::TextAlignmentRole:
        if (row == 1 && col == 1) // change text alignment only for cell(1,1)
            return int(Qt::AlignRight | Qt::AlignVCenter);
        break;
    case Qt::CheckStateRole:
        if (row == 1 && col == 0) // add a checkbox to cell(1,0)
            return Qt::Checked;
        break;
    }
    return QVariant();
}

各フォーマット・プロパティは、data() メソッドへの個別の呼び出しでモデルから要求されます。role パラメータは、どのプロパティが要求されているかをモデルに知らせるために使用されます:

enum Qt::ItemDataRole意味タイプ
Qt::DisplayRoleテキストQString
Qt::FontRoleフォントQFont
BackgroundRoleセルの背景のブラシ。QBrush
Qt::TextAlignmentRoleテキストの配置enum Qt::AlignmentFlag
Qt::CheckStateRoleQVariant()

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

またはQt::Unchecked

enum Qt::ItemDataRole

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

それでは、分離されたモデルを使用することがアプリケーションのパフォーマンスにどのような影響を与えるかを調べる必要があるので、ビューがdata() メソッドを呼び出す頻度を追跡してみましょう。ビューがモデルを呼び出す頻度を追跡するために、data() メソッドにデバッグ文を記述し、エラー出力ストリームにログを記録します。この小さな例では、data ()が42回呼び出されます。カーソルをフィールドの上に置くたびに、data() が再度呼び出されます。そのため、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秒に設定し、タイムアウトシグナルを接続します。

(ファイルソース: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 最小限の編集例

この例では、テーブル・セルに入力された値を繰り返すことで、ウィンドウ・タイトルに内容を自動的に入力するアプリケーションを作成します。ウィンドウ・タイトルに簡単にアクセスできるように、QTableViewQMainWindow に置きます。

このモデルは編集機能が利用できるかどうかを決定します。利用可能な編集機能を有効にするためには、モデルを修正するだけでよい。これは、次の仮想メソッドを再実装することによって行われます: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 二次元配列QString m_gridData を使ってデータを格納します。これはm_gridDataMyModel のコアにしています。MyModel の残りの部分はラッパーのように振る舞い、m_gridDataQAbstractItemModel インターフェースに適応させています。また、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 は編集の結果を示します。セルはテキストしか含まないので、ロールは常にQt::EditRole に設定されます。チェックボックスが存在し、チェックボックスを選択できるようにユーザー権限が設定されている場合、ロールは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

上記の例を、ツリー・ビューを持つアプリケーションに変換することができます。QTableViewQTreeView に置き換えるだけで、読み書き可能なツリーになります。モデルに変更を加える必要はありません。モデル自体に階層がないため、ツリーには階層がありません。

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

これが、私たちのモデルの例です:

本物のツリーを見せたい。上記の例では、モデルを作成するためにデータをラップしました。今回は、QStandardItemModel を使用します。これは、QAbstractItemModel を実装した階層データのコンテナです。ツリーを表示するには、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 、コンストラクタにQStandardItemsQStandardItem 、他の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は一般的なデータ構造に対して定義済みのモデルも提供しています。利用可能なデータ構造の一つがアプリケーションに適している場合、定義済みモデルは良い選択となります。

QStringListModel文字列のリストを格納する
QStandardItemModel任意の階層項目を格納
QFileSystemModelローカルファイルシステムをカプセル化する
QSqlQueryModelSQL結果セットをカプセル化する
QSqlTableModelSQLテーブルをカプセル化する
QSqlRelationalTableModel外部キーを持つ SQL テーブルをカプセル化する
QSortFilterProxyModel別のモデルをソートまたはフィルタリングする

3.4 デリゲート

ここまでのすべての例で、データはセル内のテキストまたはチェックボックスとして表示され、テキストまたはチェックボックスとして編集されます。これらの表示と編集のサービスを提供するコンポーネントをデリゲートと呼びます。ビューはデフォルトのデリゲートを使用しているので、デリゲートを扱うのはこれからです。しかし、別のエディタ(例えば、スライダーやドロップダウンリスト)を使いたい場合や、データをグラフィックスとして表示したい場合を想像してみてください。スターデリゲートと呼ばれる例を見てみましょう:

このビューにはsetItemDelegate() メソッドがあり、デフォルトのデリゲートを置き換えてカスタムのデリゲートをインストールします。新しいデリゲートを作成するには、QStyledItemDelegate を継承したクラスを作成します。

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 Documentation を参照してください:

3.5 ModelTestを使ったデバッグ

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

Qt Labsでは、プログラミングの実行中にモデルをチェックするModelTestというソフトウェアを提供しています。モデルが変更されるたびに、ModelTestはモデルをスキャンし、アサートでエラーを報告します。これは、ツリーモデルにとって特に重要です。なぜなら、ツリーモデルは階層的な性質を持っているため、微妙な矛盾が発生する可能性が多く残されているからです。

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

4.追加情報の良い情報源

4.1 書籍

モデル/ビュー・プログラミングは、Qtのドキュメントでかなり広範囲にカバーされていますが、良書もいくつかあります。

  1. C++ GUI Programming with Qt 4/ Jasmin Blanchette, Mark Summerfield,Prentice Hall, 2nd edition, ISBN 0-13-235416-0.ドイツ語版もあります:C++ GUI Programmierung mit Qt 4: Die offizielle Einführung,Addison-Wesley, ISBN 3-827327-29-6.
  2. The Book of Qt4, The Art of Building Qt Applications/ Daniel Molkentin,Open Source Press, ISBN 1-59327-147-6.Qt 4, Einführung in die Applikationsentwicklung,Open Source Press, ISBN 3-937514-12-0 より翻訳。
  3. Foundations of Qt Development/ Johan Thelin,Apress, ISBN 1-59059-831-8.
  4. Advanced Qt Programming/ Mark Summerfield,Prentice Hall, ISBN 0-321-63590-6.この本では、モデル/ビュー・プログラミングを150ページ以上にわたって扱っています。

以下のリストは、上記の最初の3冊に含まれているサンプル・プログラムの概要です。これらのいくつかは、同様のアプリケーションを開発するための非常に良いテンプレートとなります。

例題名使用ビュークラス使用モデル対象アスペクト
チームリーダーQリストビューQStringListModel第1巻 第10章 図10.6
色の名前QListViewQSortFilterProxyModel に適用QStringListModel第1巻 第10章 図10.8
通貨QTableViewに基づくカスタムモデルQAbstractTableModel読み取り専用第1巻 第10章 図10.10
都市QTableViewに基づくカスタムモデルQAbstractTableModel読み取り/書き込み第1巻 第10章 図10.12
ブール・パーサーQTreeViewに基づくカスタムモデルQAbstractItemModel読み取り専用第1巻 第10章 図10.14
トラックエディタQTableWidgetカスタムエディタを提供するカスタムデリゲート第1巻 第10章 図10.15
アドレス帳QListView QTableView QTreeViewに基づくカスタムモデルQAbstractTableModel読み取り/書き込みBook2, 8.4章
ソート付きアドレス帳QSortfilterProxyModelソートとフィルタ機能の紹介書籍2 8.5章
チェックボックス付きアドレス帳モデル/ビューにチェックボックスを導入する書籍2 8.6章
グリッドを転置したアドレス帳に基づくカスタムプロキシモデルQAbstractProxyModelカスタムモデルを導入する書籍2 8.7章
ドラッグ&ドロップによるアドレス帳ドラッグ&ドロップのサポートBook2 8.8章
カスタムエディタによるアドレス帳カスタムデリゲートの紹介Book2 8.9章
ビューQListView QTableView QTreeViewQStandardItemModel閲覧のみ第3巻 第5章 図5-3
バーデリゲートQTableViewに基づくプレゼンテーション用のカスタムデリゲート。QAbstractItemDelegate第3巻 第5章 図5-5
編集デリゲートQTableViewに基づく編集のためのカスタムデリゲートQAbstractItemDelegate第3巻 第5章 図5-6
単一項目ビューに基づくカスタムビューQAbstractItemViewカスタムビュー第3巻 第5章 図5-7
リストモデルQTableViewに基づくカスタムモデルQAbstractTableModel読み取り専用第3巻 第5章 図5-8
トレモデルQTreeViewに基づくカスタムモデルQAbstractItemModel読み取り専用第3巻 第5章 図5-10
整数の編集QListViewに基づくカスタムモデルQAbstractListModel読み取り/書き込み第3章、第5節、リスト5-37、図5-11
ソートQTableViewQSortFilterProxyModel に適用されるQStringListModelソートのデモ第3章、第5節、図5-12

4.2 Qtドキュメント

Qt 5.0 には、model/view に関する 19 のサンプルがあります。これらの例は、Item Views Examplesのページにあります。

使用するビュークラス使用モデル対象アスペクト
アドレス帳QTableViewQAbstractTableModel QSortFilterProxyModelQSortFilterProxyModel 、1つのデータプールから異なるサブセットを生成する。
基本的なソート/フィルターモデルQTreeViewQStandardItemModel QSortFilterProxyModel
チャートカスタムビューQStandardItemModel選択モデルと協調するカスタムビューの設計
カラーエディターファクトリーQTableWidget色を選択するための新しいカスタムエディタで標準のデリゲートを強化する
コンボウィジェットマッパーQDataWidgetMapper QLineEdit 、 、 をマッピングする。QTextEdit QComboBoxQStandardItemModelQComboBox がどのようにビュークラスとして機能するかを示す
カスタムソート/フィルタモデルQTreeViewQStandardItemModel QSortFilterProxyModel高度なソートとフィルタリングのためのサブクラスQSortFilterProxyModel
ディレクトリビューQTreeViewQFileSystemModelモデルをビューに割り当てる方法を示す非常に小さな例
編集可能なツリーモデルQTreeViewカスタムツリーモデルツリーを扱うための包括的な例で、基礎となるカスタムモデルによるセルとツリー構造の編集を示します。
詳細取得QListViewカスタムリストモデル動的に変化するモデル
凍結カラムQTableViewQStandardItemModel
インタビューマルチプルカスタム項目モデル複数のビュー
ピクセルレーターQTableViewカスタムテーブルモデルカスタムデリゲートの実装
パズルQListViewカスタムリストモデルドラッグ&ドロップによるモデル/ビュー
シンプルなDOMモデルQTreeViewカスタムツリーモデルカスタムツリーモデルの読み取り専用サンプル
単純なツリーモデルQTreeViewカスタム・ツリー・モデルカスタム・ツリー・モデルの読み取り専用例
シンプル・ウィジェット・マッパーQDataWidgetMapper QLineEdit, およびQTextEdit QSpinBoxQStandardItemModelQDataWidgetMapper 基本的な使い方
スプレッドシートQTableViewカスタムデリゲート
スターデリゲートQTableWidget包括的なカスタムデリゲートの例。

モデル/ビュー技術のリファレンス・ドキュメントもあります。

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