QtクイックビューでC++モデルを使う

カスタムC++モデルで提供されるデータ

C++ でモデルを定義し、QML で利用することができます。これは、既存の C++ データモデルや複雑なデータセットを QML で利用する場合に便利です。

C++のモデルクラスは、QStringListQVariantListQObjectListQAbstractItemModel のように定義することができます。最初の3つは単純なデータセットを公開するのに便利ですが、QAbstractItemModel はより複雑なモデルに対してより柔軟なソリューションを提供します。

以下は、C++モデルをQMLに公開する全過程を説明するビデオチュートリアルです:

QStringListベースのモデル

モデルは単純なQStringListmodelDataロールを介してリストの内容を提供します。

以下は、modelData ロールを使ってモデル項目の値を参照するデリゲートを持つListView です:

ListView {
    width: 100
    height: 100
    required model

    delegate: Rectangle {
        required property string modelData
        height: 25
        width: 100
        Text { text: parent.modelData }
    }
}

Qtアプリケーションは、このQMLドキュメントを読み込み、myModel の値をQStringList に設定することができます:

    QStringList dataList = {
        "Item 1",
        "Item 2",
        "Item 3",
        "Item 4"
    };

    QQuickView view;
    view.setInitialProperties({{ "model", QVariant::fromValue(dataList) }});

この例の完全なソースコードは、Qtインストールディレクトリのexamples/quick/models/stringlistmodelにあります。

注: QStringList の内容が変更されたことをビューが知る方法はありません。QStringList が変更された場合、ビューのmodel プロパティを再度設定することで、モデルをリセットする必要があります。

QVariantListベースのモデル

モデルは、modelDataロールを介してリストの内容を提供する、単一のQVariantList

API は、前のセクションで示したQStringList と同様に動作します。

注: QVariantList の内容が変更されたことをビューが知る方法はありません。QVariantList が変更された場合、モデルをリセットする必要があります。

QObjectListベースのモデル

QObject* の値のリストもモデルとして使用することができます。QList<QObject*>は、リスト内のオブジェクトのプロパティをロールとして提供します。

次のアプリケーションでは、QList<DataObject*> が QML に公開されたときに、名前付きロールとしてアクセスできるQ_PROPERTY 値を持つDataObject クラスを作成します:

class DataObject : public QObject
{
    Q_OBJECT

    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
    Q_PROPERTY(QString color READ color WRITE setColor NOTIFY colorChanged)
    ...
};

int main(int argc, char ** argv)
{
    QGuiApplication app(argc, argv);

    const QStringList colorList = {"red",
                                   "green",
                                   "blue",
                                   "yellow"};

    const QStringList moduleList = {"Core", "GUI", "Multimedia", "Multimedia Widgets", "Network",
                                    "QML", "Quick", "Quick Controls", "Quick Dialogs",
                                    "Quick Layouts", "Quick Test", "SQL", "Widgets", "3D",
                                    "Android Extras", "Bluetooth", "Concurrent", "D-Bus",
                                    "Gamepad", "Graphical Effects", "Help", "Image Formats",
                                    "Location", "Mac Extras", "NFC", "OpenGL", "Platform Headers",
                                    "Positioning", "Print Support", "Purchasing", "Quick Extras",
                                    "Quick Timeline", "Quick Widgets", "Remote Objects", "Script",
                                    "SCXML", "Script Tools", "Sensors", "Serial Bus",
                                    "Serial Port", "Speech", "SVG", "UI Tools", "WebEngine",
                                    "WebSockets", "WebView", "Windows Extras", "XML",
                                    "XML Patterns", "Charts", "Network Authorization",
                                    "Virtual Keyboard", "Quick 3D", "Quick WebGL"};

    QList<QObject *> dataList;
    for (const QString &module : moduleList)
        dataList.append(new DataObject("Qt " + module, colorList.at(rand() % colorList.length())));

    QQuickView view;
    view.setResizeMode(QQuickView::SizeRootObjectToView);
    view.setInitialProperties({{ "model", QVariant::fromValue(dataList) }});
    ...

QObject* はmodelData プロパティとして利用できます。便宜上、オブジェクトのプロパティはデリゲートのコンテキストでも直接利用できるようになっています。ここでは、view.qmlListView デリゲートのDataModel プロパティを参照しています:

ListView {
    id: listview
    width: 200; height: 320
    required model
    ScrollBar.vertical: ScrollBar { }

    delegate: Rectangle {
        width: listview.width; height: 25

        required color
        required property string name

        Text { text: parent.name }
    }
}

color プロパティを使用していることに注意してください。派生型のrequired として宣言することで、既存のプロパティを要求することができます。

この例の完全なソースコードは、Qt インストール・ディレクトリのexamples/quick/models/objectlistmodelにあります。

注:QList の内容が変更されたことをビューが知る方法はありません。QList が変更された場合、model プロパティを再度設定することで、モデルをリセットする必要があります。

QAbstractItemModel サブクラス

モデルは、QAbstractItemModel をサブクラス化することによって定義することができます。これは、他のアプローチでは対応できないような複雑なモデルがある場合に最適なアプローチです。また、QAbstractItemModel は、モデルデータが変更された際に、自動的に QML ビューに通知することもできます。

QAbstractItemModel::roleNames() を再実装することで、QAbstractItemModel のサブクラスの役割を QML に公開することができます。

以下は、QAbstractListModel のサブクラスであるAnimalModel を持つアプリケーションです。これはQAbstractItemModel::roleNames() を再実装してロール名を公開し、QMLからアクセスできるようにしています:

class Animal
{
public:
    Animal(const QString &type, const QString &size);
    ...
};

class AnimalModel : public QAbstractListModel
{
    Q_OBJECT
public:
    enum AnimalRoles {
        TypeRole = Qt::UserRole + 1,
        SizeRole
    };

    AnimalModel(QObject *parent = nullptr);
    ...
};

QHash<int, QByteArray> AnimalModel::roleNames() const {
    QHash<int, QByteArray> roles;
    roles[TypeRole] = "type";
    roles[SizeRole] = "size";
    return roles;
}

int main(int argc, char ** argv)
{
    QGuiApplication app(argc, argv);

    AnimalModel model;
    model.addAnimal(Animal("Wolf", "Medium"));
    model.addAnimal(Animal("Polar bear", "Large"));
    model.addAnimal(Animal("Quoll", "Small"));

    QQuickView view;
    view.setResizeMode(QQuickView::SizeRootObjectToView);
    view.setInitialProperties({{"model", QVariant::fromValue(&model)}});
    ...

このモデルは、型と サイズのロールにアクセスするListView デリゲートによって表示されます:

ListView {
    width: 200; height: 250

    required model

    delegate: Text {
        required property string type
        required property string size

        text: "Animal: " + type + ", " + size
    }
}

モデルが変更されると、QMLビューは自動的に更新されます。モデルが変更された場合、QAbstractItemModel::dataChanged()やQAbstractItemModel::beginInsertRows()などを使用して、モデルが変更されたことをビューに通知します。モデルは、モデルの変更に関する標準的なルールに従う必要があることを覚えておいてください。詳細については、モデルのサブクラス化のリファレンスを参照してください。

この例の完全なソースコードは、Qtのインストールディレクトリのexamples/quick/models/abstractitemmodelにあります。

QAbstractItemModel はテーブルの階層構造を表示しますが、現在QMLが提供しているビューはリストデータしか表示することができません。階層モデルの子リストを表示するためには、 QML型を使用します。この型は、 型のリストモデルで使用するために、以下のプロパティと関数を提供します:DelegateModel QAbstractItemModel

SQL モデル

Qt は SQL データモデルをサポートする C++ クラスを提供します。これらのクラスは、基礎となる SQL データに対して透過的に動作するため、作成、挿入、更新などの基本的な SQL 操作のために SQL クエリを実行する必要性が低くなります。これらのクラスの詳細については、「SQL モデル・クラスの使用」を参照してください。

C++ クラスは SQL データを操作するための完全な機能セットを提供しますが、QML へのデータアクセスは提供しません。そのため、C++のカスタムデータモデルをこれらのクラスのサブクラスとして実装し、型やコンテキストプロパティとしてQMLに公開する必要があります。

読み取り専用のデータモデル

QMLから読み取り専用でデータにアクセスできるようにするために、カスタムモデルは以下のメソッドを再実装する必要があります:

  • roleNames() を用いて、QMLフロントエンドにロール名を公開します。例えば、以下のバージョンでは、選択されたテーブルのフィールド名をロール名として返します:
     QHash<int, QByteArray> SqlQueryModel::roleNames() const
     {
        QHash<int, QByteArray> roles;
        // record() returns an empty QSqlRecord
        for (int i = 0; i < this->record().count(); i ++) {
            roles.insert(Qt::UserRole + i + 1, record().fieldName(i).toUtf8());
        }
        return roles;
    }
  • data(SQLデータをQMLフロントエンドに公開します。例えば、以下の実装は与えられたモデルインデックスのデータを返します:
    QVariant SqlQueryModel::data(const QModelIndex &index, int role) const
    {
        QVariant value;
    
        if (index.isValid()) {
            if (role < Qt::UserRole) {
                value = QSqlQueryModel::data(index, role);
            } else {
                int columnIdx = role - Qt::UserRole - 1;
                QModelIndex modelIndex = this->index(index.row(), columnIdx);
                value = QSqlQueryModel::data(modelIndex, Qt::DisplayRole);
            }
        }
        return value;
    }

QSqlQueryModel クラスは、SQL データベースのデータを表すカスタム読み取り専用モデルを実装するのに十分です。チャットのチュートリアルの例では、SQLiteデータベースから連絡先の詳細を取得するカスタムモデルを実装することで、このことをよく示しています。

編集可能なデータモデル

QSqlTableModel は setData() を実装しています

モデルによって使用されるEditStrategy によって、変更は後で送信するためにキューに入れられるか、すぐに送信されます。

QSqlTableModel::insertRecord() を呼び出すことで、モデルに新しいデータを挿入することもできます。以下のスニペット例では、QSqlRecord に本の詳細が入力され、モデルに追加されています:

...
QSqlRecord newRecord = record();
newRecord.setValue("author", "John Grisham");
newRecord.setValue("booktitle", "The Litigators");
insertRecord(rowCount(), newRecord);
...

C++データモデルのQMLへの公開

上記の例では、ビューの必須プロパティを使用して、QMLコンポーネントに直接モデルの値を設定しました。これに代わる方法として、C++のモデルクラスをQMLの型として登録する方法があります(C++からQMLの型を定義するを参照)。これにより、モデルクラスをQMLの中で型として直接作成することができるようになります:

C++
class MyModel : public QAbstractItemModel
{
    Q_OBJECT
    QML_ELEMENT

    // [...]
}
QML
MyModel {
    id: myModel
}
ListView {
    width: 200; height: 250
    model: myModel
    delegate: Text {
        required property string someProperty
        text: someProperty
    }
}

C++でQML型を記述する方法については、「C++でQML拡張を記述する」をご参照ください。

モデルデータの変更

roleNames()data() の他に、編集可能なモデルは、既存のモデルデータの変更を保存するた めにsetData メソッドを再実装する必要があります。以下のバージョンのメソッドでは、与えられたモデルインデックスが有効で、roleQt::EditRole と等しいかどうかをチェックします:

bool EditableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (index.isValid() && role == Qt::EditRole) {
        // Set data in model here. It can also be a good idea to check whether
        // the new value actually differs from the current value
        if (m_entries[index.row()] != value.toString()) {
            m_entries[index.row()] = value.toString();
            emit dataChanged(index, index, { Qt::EditRole, Qt::DisplayRole });
            return true;
        }
    }
    return false;
}

注意: 変更を保存した後にdataChanged() シグナルを発することが重要です。

QListViewQTableView のようなC++のアイテムビューとは異なり、setData() メソッドはQMLのデリゲートから明示的に呼び出す必要があります。これは、単純に対応するモデルのプロパティに新しい値を代入することで行われます。

ListView {
    anchors.fill: parent
    model: EditableModel {}
    delegate: TextField {
        width: ListView.view.width
        text: model.edit
        onAccepted: model.edit = text
    }
}

注: edit のロールはQt::EditRole と同じです。組み込みのロール名についてはroleNames() を参照してください。しかし、実際のモデルでは、カスタム・ロールを登録するのが普通でしょう。

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