モデル/ビューのチュートリアル
すべてのUI開発者はModelViewプログラミングについて知っている必要があり、このチュートリアルのゴールは、このトピックへのわかりやすい入門を提供することです。
テーブル、リスト、ツリーウィジェットは、GUI で頻繁に使用されるコンポーネントです。これらのウィジェットがデータにアクセスする方法は2種類あります。伝統的な方法は、データを格納する内部コンテナを含むウィジェットです。このアプローチは非常に直感的ですが、多くの非自明なアプリケーションでは、データ同期の問題につながります。第2のアプローチは、モデル/ビュー・プログラミングで、ウィジェットは内部データ・コンテナを保持しません。標準化されたインターフェースを通して外部データにアクセスするため、データの重複を避けることができます。最初は複雑に見えるかもしれませんが、よく見てみると、理解しやすいだけでなく、モデル/ビュー・プログラミングの多くの利点が明らかになります。
その過程で、Qtが提供するいくつかの基本的な技術について学びます:
- 標準ウィジェットとモデル/ビューウィジェットの違い
- フォームとモデル間のアダプタ
- 簡単なモデル/ビューアプリケーションの開発
- 定義済みのモデル
- 次のような中級トピック
- ツリービュー
- 選択
- デリゲート
- モデルテストによるデバッグ
また、新しいアプリケーションがモデル/ビュープログラミングで簡単に書けるのか、それとも古典的なウィジェットでも同じように動作するのかについても学ぶことができます。
このチュートリアルには、編集してプロジェクトに組み込むためのサンプルコードが含まれています。チュートリアルのソースコードは Qt のexamples/widgets/tutorials/modelviewディレクトリにあります。
より詳細な情報については、リファレンスドキュメントを参照してください。
1.はじめに
Model/Viewは、データセットを扱うウィジェットにおいて、データとビューを分離するために使用される技術です。標準的なウィジェットは、データとビューを分離するようには設計されていませんので、Qt には 2 種類のウィジェットがあります。どちらのタイプのウィジェットも見た目は同じですが、データの扱い方が異なります。
標準ウィジェットは、ウィジェットの一部であるデータを使用します。 | |
ビュークラスは外部データ(モデル)を操作します。 |
1.1 標準ウィジェット
標準のテーブル・ウィジェットを詳しく見てみましょう。テーブル・ウィジェットは、ユーザが変更できるデータ要素の2次元配列です。テーブル・ウィジェットは、テーブル・ウィジェットが提供するデータ要素を読み書きすることで、プログラム・フローに組み込むことができます。この方法は非常に直感的で多くのアプリケーションで有用ですが、標準的なテーブル・ウィジェットでデータベース・テーブルを表示したり編集したりするには問題があります。一つはウィジェットの外側、もう一つはウィジェットの内側です。開発者は、両方のバージョンを同期させる責任があります。これに加えて、プレゼンテーションとデータの緊密な結合は、ユニットテストを書くことを難しくします。
1.2 モデル/ビューによる救済
モデル/ビューは、より汎用的なアーキテクチャを使用するソリューションを提供するために立ち上がった。Model/viewは、標準的なウィジェットで起こりうるデータの一貫性の問題を排除します。また、モデル/ビューは、1つのモデルを多くのビューに渡すことができるため、同じデータに対して複数のビューを使用することが容易になります。最も重要な違いは、モデル/ビューウィジェットはテーブルセルの後ろにデータを保存しないことです。実際、ウィジェットはデータから直接操作します。ビュークラスはデータの構造を知らないので、データをQAbstractItemModel インターフェースに適合させるラッパーを提供する必要があります。ビューはこのインターフェイスを使用して、データからの読み込みとデータへの書き込みを行います。QAbstractItemModel を実装したクラスのインスタンスは、モデルであると言われます。ビューはモデルへのポインタを受け取ると、その内容を読み込んで表示し、そのエディタとなります。
1.3 モデル/ビュー・ウィジェットの概要
モデル/ビュー・ウィジェットと、それに対応する標準ウィジェットの概要を示します。
ウィジェット | 標準ウィジェット (アイテムベースの便利なクラス) | モデル/ビュービュークラス (外部データで使うため) |
---|---|---|
![]() | QListWidget | QListView |
![]() | QTableWidget | QTableView |
![]() | QTreeWidget | QTreeView |
![]() | 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 に設定されます。その他のロールは次のセクションで説明します。この例では、表示すべきデータが生成されます。実際のアプリケーションでは、MyModel
はMyData
というメンバを持ち、このメンバがすべての読み書き操作のターゲットとなります。
この小さな例は、モデルの受動的な性質を示しています。モデルはいつ使用されるのか、どのデータが必要なのかを知りません。ビューが要求するたびにデータを提供するだけです。
モデルのデータを変更する必要がある場合はどうなるでしょうか?データが変更され、再度読み込む必要があることをビューはどのように認識するのでしょうか?モデルは、セルのどの範囲が変更されたかを示すシグナルを出さなければなりません。これについては2.3節で説明する。
2.2 ロールによる読み取り専用モデルの拡張
ビューが表示するテキストを制御することに加えて、モデルはテキストの外観も制御します。モデルを少し変更すると、次のようになります:
実際には、data ()メソッド以外は、フォント、背景色、整列、チェックボックスを設定するために変更する必要はありません。以下は、上記の結果を生成するdata ()メソッドです。違いは、今回はパラメータint roleを使用して、その値によって異なる情報を返すようにしている点です。
(ファイル・ソース:examples/widgets/tutorials/modelview/2_formatting/mymodel.cpp)
// mymodel.cppQVariantMyModel::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("Row%1, Column%2").arg(row+ 1).arg(col+1);caseQt::FontRole:if(row== 0 &&col== 0) {// cell(0,0)に対してのみフォントを変更する。 QFontboldFont; boldFont.setBold(true);returnboldFont; }break;caseQt::BackgroundRole:if(row== 1 &&col== 2)// cell(1,2)の背景のみを変更 returnQBrush(Qt::red);break;caseQt::TextAlignmentRole:if(row== 1 &&col== 1)// cell(1,1)のテキスト配置のみ変更 return int(Qt::右揃えQt::AlignVCenter);break;caseQt::CheckStateRole:if(row== 1 &&col== 0)// cell(1,0)にチェックボックスを追加 returnQt::Checked;break; }returnQVariant(); }
各フォーマット・プロパティは、data ()メソッドへの個別の呼び出しでモデルから要求されます。role
パラメータは、どのプロパティが要求されているかをモデルに知らせるために使用されます:
enum Qt::ItemDataRole | 意味 | タイプ |
---|---|---|
Qt::DisplayRole | テキスト | QString |
Qt::FontRole | フォント | QFont |
BackgroundRole | セルの背景用ブラシ | QBrush |
Qt::TextAlignmentRole | テキストの配置 | enum Qt::AlignmentFlag |
Qt::CheckStateRole | QVariant() 、 でチェックボックスを設定します。Qt::Checked | 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 最小限の編集例
この例では、テーブル・セルに入力された値を繰り返すことで、ウィンドウ・タイトルにコンテンツが自動的に入力されるアプリケーションを作成します。ウィンドウ・タイトルに簡単にアクセスできるように、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
二次元配列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
は編集の結果を示します。セルはテキストしか含まないので、ロールは常に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
上記の例を、ツリー・ビューを持つアプリケーションに変換することができます。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は一般的なデータ構造に対して定義済みのモデルも提供しています。利用可能なデータ構造の一つがアプリケーションに適している場合、定義済みモデルは良い選択となります。
QStringListModel | 文字列のリストを格納する |
QStandardItemModel | 任意の階層項目を格納 |
QFileSystemModel | ローカルファイルシステムをカプセル化する |
QSqlQueryModel | SQL結果セットをカプセル化する |
QSqlTableModel | SQLテーブルをカプセル化する |
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 を参照してください:
- デリゲートクラス
- QAbstractItemDelegate Class Reference
- QSqlRelationalDelegate Class Reference
- QStyledItemDelegate Class Reference
- QItemDelegate Class Reference
3.5 ModelTestを使ったデバッグ
モデルの受動的な性質は、プログラマーに新たな課題を提供します。モデルの不整合は、アプリケーションのクラッシュを引き起こす可能性があります。モデルはビューからの多数の呼び出しによってヒットされるため、どの呼び出しがアプリケーションをクラッシュさせたのか、どの操作が問題を引き起こしたのかを見つけるのは困難です。
Qt Labsは、プログラミングの実行中にモデルをチェックするModelTestというソフトウェアを提供しています。モデルが変更されるたびに、ModelTestはモデルをスキャンし、アサートでエラーを報告します。これは、ツリーモデルにとって特に重要です。なぜなら、ツリーモデルの階層的な性質は、微妙な不整合の可能性を多く残しているからです。
ビュークラスとは異なり、ModelTestはモデルをテストするために範囲外のインデックスを使用します。このことは、ModelTestを使用しなくてもアプリケーションは問題なく動作していたとしても、ModelTestを使用するとアプリケーションがクラッシュする可能性があることを意味します。そのため、ModelTestを使用する際には、範囲外のインデックスをすべて処理する必要もあります。
4.追加情報の良い情報源
4.1 書籍
モデル/ビュー・プログラミングは、Qtのドキュメントでかなり広範囲にカバーされていますが、良書もいくつかあります。
- 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.
- 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 より翻訳。
- Foundations of Qt Development/ Johan Thelin,Apress, ISBN 1-59059-831-8.
- Advanced Qt Programming/ Mark Summerfield,Prentice Hall, ISBN 0-321-63590-6.この本では、モデル/ビュー・プログラミングを150ページ以上にわたって扱っています。
以下のリストは、上記の最初の3冊に含まれているサンプル・プログラムの概要です。これらのいくつかは、同様のアプリケーションを開発するための非常に良いテンプレートになります。
例題名 | 使用ビュークラス | 使用モデル | 対象アスペクト | |
---|---|---|---|---|
チームリーダー | Qリストビュー | QStringListModel | 第1巻 第10章 図10.6 | |
色の名前 | QListView | QSortFilterProxyModel に適用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 QTreeView | QStandardItemModel | 閲覧のみ | 第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 |
ソート | QTableView | QSortFilterProxyModel に適用されるQStringListModel | ソートのデモ | 第3章、第5節、図5-12 |
4.2 Qtドキュメント
Qt 5.0 には、model/view に関する 19 のサンプルがあります。これらの例は、Item Views Examplesのページにあります。
例 | 使用するビュークラス | 使用モデル | 対象アスペクト |
---|---|---|---|
アドレス帳 | QTableView | QAbstractTableModel QSortFilterProxyModel | QSortFilterProxyModel 、1つのデータプールからさまざまなサブセットを生成する。 |
基本的なソート/フィルターモデル | QTreeView | QStandardItemModel QSortFilterProxyModel | |
チャート | カスタムビュー | QStandardItemModel | 選択モデルと協調するカスタムビューの設計 |
カラーエディターファクトリー | QTableWidget | 色を選択するための新しいカスタムエディタで標準のデリゲートを強化する | |
コンボウィジェットマッパー | QDataWidgetMapper QLineEdit 、 、 をマッピングする。QTextEdit QComboBox | QStandardItemModel | QComboBox がどのようにビュークラスとして機能するかを示す |
カスタムソート/フィルタモデル | QTreeView | QStandardItemModel QSortFilterProxyModel | 高度なソートとフィルタリングのためのサブクラスQSortFilterProxyModel |
ディレクトリビュー | QTreeView | QFileSystemModel | モデルをビューに割り当てる方法を示す非常に小さな例 |
編集可能なツリーモデル | QTreeView | カスタムツリーモデル | ツリーを扱うための包括的な例で、基礎となるカスタムモデルによるセルとツリー構造の編集を示します。 |
詳細取得 | QListView | カスタムリストモデル | 動的に変化するモデル |
凍結カラム | QTableView | QStandardItemModel | |
インタビュー | マルチプル | カスタム項目モデル | 複数のビュー |
ピクセルレーター | QTableView | カスタムテーブルモデル | カスタムデリゲートの実装 |
パズル | QListView | カスタムリストモデル | ドラッグ&ドロップによるモデル/ビュー |
シンプルなDOMモデル | QTreeView | カスタムツリーモデル | カスタムツリーモデルの読み取り専用サンプル |
単純なツリーモデル | QTreeView | カスタム・ツリー・モデル | カスタム・ツリー・モデルの読み取り専用例 |
シンプル・ウィジェット・マッパー | QDataWidgetMapper QLineEdit, およびQTextEdit QSpinBox | QStandardItemModel | QDataWidgetMapper 基本的な使い方 |
スプレッドシート | QTableView | カスタムデリゲート | |
スターデリゲート | QTableWidget | 包括的なカスタムデリゲートの例。 |
モデル/ビュー技術のリファレンスドキュメントもあります。
© 2025 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.