QMLのTableView(と言うか、TableViewが参照するModel)の使い方が、ドキュメントを読むだけではわかりにくかったので、簡単なサンプルを作ってみた。
https://github.com/false-git/qtsamples の、TableViewDemo に置いてある。
ビルドして起動すると、二つのTableViewが表示される。
左が QML の ListModel を Model とした TableView で、右が C++ の QAbstractListModel を Model とした TableView である。
画面下部にテキストフィールドとボタンがあり、行の追加、更新、削除ができるようになっている。
また、アプリケーションのメニューに Settings と言うものがあり、そこでソートのあり/なしを選ぶことができる。
ソースの読み方
ListModel
ListModelのソースは、QmlListModelTableView.qml のみである。
ListModelにはソートの機能がないため、http://qt-project.org/forums/viewthread/11108 のコメントで紹介されていた、Javascriptでのquick sortの実装を入れてある。
件数が少ない場合はこれで問題ないが、多くなると速度的に厳しくなってくるだろう。
見るべきところは、TableViewの onCurrentRowChanged, appendRow, updateRow, deleteRowである。
モデルからのデータの取得
ListModelのget()を使う。
モデルへのデータの追加
Javascriptのオブジェクトを作成し、ListModelのappend()を使う。
モデルのデータ更新
ListModelのsetProperty()を使う。
モデルからのデータの削除
ListModelのremove()を使う。
QAbstractListModel
QML側のソースは、QAbstractListModelTableView.qmlである。
C++側のソースは、personlistmodel.h/personlistmodel.cpp/main.cppである。
personlistmodel.h/cpp で、PersonListModelと言うクラスを作成している。これは、QAbstractListModelのサブクラスである。
メンバとしてQListを持ち、ここにモデルのデータを格納する。
QAbstractListModelはabstract classなので、最低限以下の関数の実装が必要である。
- rowCount() - モデルが持つデータの件数(行数)を返す
- data() - データを返す
- roleNames() - role名のHashを返す
Tableと言うのは一般的にrow/columnsモデルであるが、QML の TableViewはrow/roleモデルである。rowが行を、roleが列を表す。(ここ、すごく気に入らないところ)
TableViewにデータを表示するだけであれば、最低限上記を実装すれば良い。
しかし、今回は追加/更新/削除を実行したいので、以下の関数を実装する。
- get() - データ取得
- appendRow() - データ追加
- updateRow() - データ更新
- removeRow() - データ削除
※ QAbstractListModelの親クラスであるQAbstractItemModelは、insertRow(), setData(), removeRow()があるので、それに合わせても良いかもしれないが、QMLのTableViewはそれらを呼んでくれないので使いやすいようにインターフェイスを変えてしまって良いと思う。
これらの関数を実装する上で重要なのは、モデルの変更をViewに伝えてやることである。
- beginInsertRows()
- endInsertRows()
のような関数が用意されており、これらを呼び出すと適切なsignalをemitしてくれる。
ただし、データの更新だけは setData() の呼び出しで dataChanged signal が emit されるのだが、今回は setData() を使用していないため、直接 dataChanged を emit している。
後は、ソートに対応するために以下のプロパティを追加する。
- sortEnabled - ソートが有効かどうか
- sortOrder - ソート順(Qt::AscendingOrder/DescendingOrder)
- sortRole - ソートする列のrole
※ ソートを実行したときに emit すべき signal についても理解できていない。今回は、beginResetRows()/endResetRows()を使った。
後は、このクラスをQMLから使えるようにするためにアプリケーションに登録してやる必要がある。
main.cpp で、QApplicationを生成した後に以下の呼び出しを追加する。
qmlRegisterType<PersonListModel>("PersonListModel", 1, 0, "PersonListModel");
これで、QML側で以下のようにimportすると他のQML Elementと同じように使うことができる。
import PersonListModel 1.0
Qt のモデルについて
ListやTableのModelは、C++では一番親に QAbstractItemModelがあり、そのサブクラスとして QAbstractListModelやQAbstractTableModelがある。
この設計思想が曲者で、QAbstractItemModel がすごく大きくて、なんでもできるようになっているのだ。
親子関係があり、RowがありColumnがありRoleがある。
これ一つでTreeViewも、TableViewも、ListViewも対応できるような設計になっている。
そして、サブクラスの QAbstractListModelやQAbstractTableModel は、どちらかと言うと巨大な親の機能を制限するような方向で設計されている。(ように見える)
さらに話をややこしくするのが、QML の TableViewがModelにQAbstractTableModelではなくQAbstractListModelを使うことだ。
これは、おそらくQMLのModelとしてはListModelのみがあり、TableModelがないため、TableViewのデータ構造として row/columnモデルではなくrow/roleモデルを採用してしまったことによると思われる。
この辺を理解してしまえば、使うことはできると思うのだが、微妙な気持ちになってしまうのである。