QML リスト表示の代替手法:Repeater と JavaScript 配列

2025-05-27

List QML Value Type (リスト QML 値型)

Qt Quick (QML) における「List QML Value Type」は、複数の異なる型の値を順番に格納できるデータ型です。これは、JavaScript の配列 (Array) に非常に近い概念を持っています。

主な特徴

  • JavaScript の配列との類似性
    QML のリストは、JavaScript の配列と同様の操作(要素へのアクセス、追加、削除、反復処理など)をサポートしています。
  • 動的なサイズ変更
    リストは必要に応じて要素を追加したり削除したりできるため、サイズが固定されている必要はありません。
  • 異なる型の値を格納可能
    文字列 (string)、整数 (int)、浮動小数点数 (real)、真偽値 (bool)、オブジェクト (object)、さらには別のリストなど、様々な型の値を一つのリストに含めることができます。
  • 順序付けられた要素の集まり
    リスト内の各要素は、リストに追加された順序を保持します。

QML でのリストの扱い方

QML では、リストは以下のように記述します。

ListModel {
    ListElement { name: "Alice"; age: 30 }
    ListElement { name: "Bob"; age: 25 }
    ListElement { name: "Charlie"; city: "New York" }
}

// または、直接 JavaScript の配列のように記述することも可能です(ただし、型安全性の面で ListModel の使用が推奨されます)。
property var myData: ["apple", 123, true, { key: "value" }]

ListModel との関連性

QML でリストデータを扱う一般的な方法は、ListModel コンポーネントを使用することです。ListModel は、リストの要素を定義するための構造を提供し、データをビュー(ListView など)に表示する際に便利です。ListElementListModel の子要素として定義することで、各要素のプロパティと値を指定できます。

一方、上記の myData の例のように、property var を使用して直接 JavaScript の配列をリストとして扱うことも可能ですが、ListModel を使用する方が、データの構造が明確になり、ビューとの連携もスムーズに行えるため、より推奨される方法です。

用途

リスト QML 値型は、以下のような様々な場面で利用されます。

  • JavaScript ロジックでのデータ操作
    JavaScript 関数内でリストを作成、操作、処理することができます。
  • 複雑なデータ構造の表現
    オブジェクトや別のリストを要素として持つことで、より複雑なデータ構造を表現できます。
  • ビューへのデータ供給
    ListViewRepeater などのビューコンポーネントに表示するデータのソースとして使用されます。


List QML Value Type における一般的なエラーとトラブルシューティング

型の不一致 (Type Mismatch)

  • トラブルシューティング
    • リストに格納する値の型を意識し、期待される型と一致しているか確認してください。
    • ListModel のデリゲート内でプロパティの型を明示的に宣言している場合は、代入する値の型が合っているか確認してください。
    • JavaScript コード内でリストを操作している場合は、意図しない型変換が行われていないか確認してください。

  • ListModel {
        Component {
            id: myDelegate
            property int value // int 型を期待
            Text { text: value }
        }
        ListElement { value: "文字列" } // エラーの可能性
    }
    
  • エラー
    リストに期待される型と異なる型の値を代入しようとした場合に発生します。QML は動的型付け言語ですが、特定のコンテキスト(例えば、C++ 側から渡されたリストや、ListModelComponent 内での型推論)では型が意識されることがあります。

インデックスの範囲外アクセス (Index Out of Bounds)

  • トラブルシューティング
    • リストの現在の要素数 (myList.lengthListModel.count) を確認し、アクセスしようとしているインデックスが 0 から length - 1 の範囲内にあるか確認してください。
    • ループ処理などでリストの要素にアクセスする場合は、ループの条件が適切であることを確認してください。

  • property var myList: [1, 2, 3]
    Component.onCompleted: {
        console.log(myList[3]) // インデックス 3 は存在しないためエラー
    }
    
  • エラー
    リストの有効なインデックス範囲外の要素にアクセスしようとした場合に発生します。

ListModel の append(), insert(), remove() などのメソッドの誤用

  • トラブルシューティング
    • 各メソッドの正しい引数の型と順序をリファレンスドキュメントで確認してください。
    • remove() を使用する場合は、削除する要素の有効なインデックスを指定してください。
    • insert() を使用する場合は、挿入先のインデックスを第一引数に指定してください。
    • append() はオブジェクトを直接渡します。

  • ListModel {
        id: myModel
        ListElement { name: "A" }
        ListElement { name: "B" }
    }
    
    Component.onCompleted: {
        myModel.remove(5) // 存在しないインデックスを指定
        myModel.insert("C") // insert の第一引数はインデックス
    }
    
  • エラー
    ListModel の要素を追加、挿入、削除するメソッドの引数が間違っている場合や、存在しないインデックスを指定した場合に予期せぬ動作やエラーが発生することがあります。

ListModel のロール (Role) の不一致

  • トラブルシューティング
    • ListModelListElement で定義したロール名(プロパティ名)と、ビューのデリゲート内で model.<ロール名> としてアクセスしている名前が完全に一致しているか確認してください。

  • ListModel {
        ListElement { title: "タイトル1"; content: "内容1" } // ロール名は title と content
    }
    
    ListView {
        model: model
        delegate: Text { text: model.name } // ロール名が異なる (name ではなく title)
    }
    
  • エラー
    ListModelListElement で定義したロール名と、ビュー(ListView など)のデリゲート内で使用しているロール名が一致しない場合に、データが表示されないなどの問題が発生します。

JavaScript でのリスト操作の誤り

  • トラブルシューティング
    • JavaScript の配列メソッド (push(), pop(), splice() など) の使い方を正しく理解し、適用してください。
    • QML のプロパティバインディングが設定されているリストに対して、JavaScript で直接要素を追加・削除すると、バインディングが解除されたり、予期せぬ動作を引き起こしたりする可能性があります。可能な限り、ListModel のメソッドを使用するか、JavaScript でリストを完全に再構築して代入するようにしてください。
  • エラー
    JavaScript コード内でリスト(property var で定義した配列など)を操作する際に、JavaScript の配列メソッドの誤用や、QML のプロパティバインディングとの競合などが発生することがあります。

C++ との連携における問題

  • トラブルシューティング
    • C++ 側でリストデータを Q_PROPERTY として公開する際には、QVariantListQQmlListProperty を適切に使用してください。
    • QML 側で受け取ったデータが期待する形式になっているか console.log() などで確認してください。
    • カスタムの QObject をリストの要素として渡す場合は、QML がそのオブジェクトのプロパティにアクセスできるように Q_PROPERTY を定義しているか確認してください。
  • エラー
    C++ 側から QML にリストデータ (QList, QVector, QStringList など) を渡す際に、型変換が正しく行われていない、または QML 側での受け取り方が間違っている場合に問題が発生することがあります。
  1. エラーメッセージの確認
    コンソールに出力されているエラーメッセージを注意深く読み、問題の原因の手がかりを探します。
  2. console.log() の活用
    問題が発生している箇所やデータの流れを追跡するために、console.log() を積極的に使用して、リストの内容や変数の値を出力してみましょう。
  3. QML デバッガーの利用
    Qt Creator に付属している QML デバッガーを使用すると、QML コードの実行をステップ実行したり、プロパティの値を監視したりすることができ、問題の特定に役立ちます。
  4. 関連するドキュメントの参照
    Qt の公式ドキュメントや QML のリファレンスを参照し、関連するコンポーネントやメソッドの正しい使い方を確認してください。
  5. 簡単なテストケースの作成
    問題を切り分けるために、最小限のコードで問題を再現できる簡単なテストケースを作成してみるのも有効です。


基本的なリストの定義とアクセス

この例では、QML で直接リスト型のプロパティを定義し、JavaScript から要素にアクセスする方法を示します。

import QtQuick 2.15
import QtQuick.Controls 2.15

Rectangle {
    width: 200
    height: 100

    property var myStringList: ["apple", "banana", "cherry"]
    property var myNumberList: [10, 20.5, 30]
    property var myMixedList: ["hello", 123, true]

    Text {
        id: textDisplay
        text: ""
        anchors.centerIn: parent
    }

    Button {
        text: "Show Item"
        anchors.bottom: parent
        anchors.horizontalCenter: parent
        onClicked: {
            let randomIndex = Math.floor(Math.random() * myStringList.length);
            textDisplay.text = "Random Fruit: " + myStringList[randomIndex];
            console.log("Number List (index 1):", myNumberList[1]);
            console.log("Mixed List (index 2):", myMixedList[2]);
        }
    }
}

説明

  • ButtononClicked ハンドラ内で、JavaScript を使用してリストの要素にランダムにアクセスし、Text 要素に表示したり、コンソールに出力したりしています。
  • property var myMixedList: ["hello", 123, true]: 異なる型の値を含むリストを定義しています。
  • property var myNumberList: [10, 20.5, 30]: 数値のリストを定義しています。
  • property var myStringList: ["apple", "banana", "cherry"]: 文字列のリストを定義しています。var 型は任意の JavaScript の値を保持できます。

ListModel を使用したリストの定義と表示

この例では、ListModel コンポーネントを使用してリストデータを定義し、ListView で表示する方法を示します。ListModel は、ビューとの連携に非常に便利です。

import QtQuick 2.15
import QtQuick.Controls 2.15

Rectangle {
    width: 200
    height: 200

    ListModel {
        id: fruitModel
        ListElement { name: "Apple"; color: "red" }
        ListElement { name: "Banana"; color: "yellow" }
        ListElement { name: "Cherry"; color: "red" }
    }

    ListView {
        anchors.fill: parent
        model: fruitModel
        delegate: Rectangle {
            width: ListView.view.width
            height: 30
            color: model.color
            Text {
                text: model.name
                anchors.centerIn: parent
            }
        }
    }
}

説明

  • delegate: リストの各要素をどのように表示するかを定義するテンプレートです。ここでは、RectangleText を使用して、要素の名前と色を表示しています。model.namemodel.color で、現在の要素のロールにアクセスできます。
  • model: fruitModel: ListView のデータソースとして fruitModel を指定しています。
  • ListView: リストデータを視覚的に表示するビューです。
  • ListElement: ListModel の各要素を定義します。ここでは namecolor というロール(プロパティ)を持っています。
  • ListModel: リストデータを管理するコンポーネントです。

ListModel の動的な操作

この例では、JavaScript を使用して ListModel の要素を動的に追加、削除する方法を示します。

import QtQuick 2.15
import QtQuick.Controls 2.15

Rectangle {
    width: 300
    height: 200

    ListModel {
        id: dynamicModel
    }

    Column {
        spacing: 10
        anchors.centerIn: parent

        TextField {
            id: nameInput
            placeholderText: "Enter Name"
        }

        Button {
            text: "Add Item"
            onClicked: {
                if (nameInput.text.trim() !== "") {
                    dynamicModel.append({ "name": nameInput.text.trim() });
                    nameInput.text = "";
                }
            }
        }

        ListView {
            width: 200
            height: 100
            model: dynamicModel
            delegate: Text { text: model.name }
        }

        Button {
            text: "Remove Last Item"
            onClicked: {
                if (dynamicModel.count > 0) {
                    dynamicModel.remove(dynamicModel.count - 1);
                }
            }
        }
    }
}

説明

  • "Remove Last Item" ボタンの onClicked ハンドラ: remove() メソッドを使用して、dynamicModel の最後の要素を削除します。dynamicModel.count で現在の要素数を取得できます。
  • ListView: dynamicModel の内容を表示します。デリゲートは名前を表示する Text 要素です。
  • "Add Item" ボタンの onClicked ハンドラ: append() メソッドを使用して、入力された名前を持つ新しい要素を dynamicModel の最後に追加します。
  • TextField: 新しいアイテムの名前を入力するためのテキストフィールドです。
  • dynamicModel: 最初は空の ListModel です。

C++ からのリストデータの受け渡し

この例は、C++ 側で作成したリストデータを QML で受け取り、表示する方法の基本的な概念を示します。(完全な動作には C++ コードが必要です)

C++ コード (例 - ヘッダーファイル)

#include <QObject>
#include <QList>
#include <QString>
#include <QQmlListProperty>

class MyData : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
public:
    MyData(QObject *parent = nullptr) : QObject(parent) {}
    QString name() const { return m_name; }
    void setName(const QString &name) { m_name = name; emit nameChanged(m_name); }
signals:
    void nameChanged(const QString &name);
private:
    QString m_name;
};

class DataProvider : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QQmlListProperty<MyData> dataList READ dataList CONSTANT)
public:
    DataProvider(QObject *parent = nullptr);
    QQmlListProperty<MyData> dataList();

    static void append_dataList(QQmlListProperty<MyData> *list, MyData *data);
    static int count_dataList(QQmlListProperty<MyData> *list);
    static MyData *at_dataList(QQmlListProperty<MyData> *list, int index);

private:
    QList<MyData*> m_dataList;
};

C++ コード (例 - ソースファイル)

#include "dataprovider.h"

DataProvider::DataProvider(QObject *parent) : QObject(parent)
{
    m_dataList.append(new MyData(this));
    m_dataList.last()->setName("Item from C++ 1");
    m_dataList.append(new MyData(this));
    m_dataList.last()->setName("Item from C++ 2");
}

QQmlListProperty<MyData> DataProvider::dataList()
{
    return QQmlListProperty<MyData>(this, nullptr,
                                     &DataProvider::append_dataList,
                                     &DataProvider::count_dataList,
                                     &DataProvider::at_dataList,
                                     nullptr);
}

void DataProvider::append_dataList(QQmlListProperty<MyData> *list, MyData *data)
{
    DataProvider *provider = static_cast<DataProvider*>(list->object);
    if (provider)
        provider->m_dataList.append(data);
}

int DataProvider::count_dataList(QQmlListProperty<MyData> *list)
{
    DataProvider *provider = static_cast<DataProvider*>(list->object);
    return provider ? provider->m_dataList.count() : 0;
}

MyData *DataProvider::at_dataList(QQmlListProperty<MyData> *list, int index)
{
    DataProvider *provider = static_cast<DataProvider*>(list->object);
    return (provider && index >= 0 && index < provider->m_dataList.count())
           ? provider->m_dataList.at(index) : nullptr;
}

QML コード

import QtQuick 2.15
import QtQuick.Controls 2.15

Rectangle {
    width: 200
    height: 150

    property DataProvider dataProvider: DataProvider {}

    ListView {
        anchors.fill: parent
        model: dataProvider.dataList
        delegate: Text { text: model.name } // C++ の MyData オブジェクトの name プロパティにアクセス
    }
}
  • QML 側では、DataProvider のインスタンスを作成し、dataProvider.dataListListViewmodel として使用しています。デリゲートでは、C++ 側の MyData オブジェクトの name プロパティに model.name としてアクセスできます。
  • MyData クラスは、リストの要素となるカスタムオブジェクトです。
  • C++ 側で DataProvider クラスを作成し、QQmlListProperty<MyData> 型の dataList プロパティを公開しています。QQmlListProperty を使用することで、QML から C++ のリストのようにアクセスできます。


Repeater と JavaScript の配列 (property var) の組み合わせ

ListView のようにリストの要素を視覚的に表示する必要がない場合や、より柔軟なレイアウトを実現したい場合には、Repeater コンポーネントと JavaScript の配列 (property var) を組み合わせて使用できます。

import QtQuick 2.15
import QtQuick.Controls 2.15

Rectangle {
    width: 200
    height: 150

    property var myDataList: ["Item 1", "Item 2", "Item 3"]

    Column {
        anchors.centerIn: parent
        spacing: 10

        Repeater {
            model: myDataList
            delegate: Text { text: modelData } // modelData で配列の各要素にアクセス
        }
    }

    Button {
        text: "Add Item"
        anchors.bottom: parent
        anchors.horizontalCenter: parent
        onClicked: {
            myDataList.push("New Item " + (myDataList.length + 1));
        }
    }
}

説明

  • ButtononClicked ハンドラ: JavaScript の push() メソッドを使用して、myDataList に新しい要素を追加しています。Repeater はモデルが変更されると自動的に更新されます。
  • delegate: Text { text: modelData }: 各要素の表示方法を定義します。JavaScript の配列をモデルとして使用する場合、各要素は modelData という名前でアクセスできます。
  • model: myDataList: Repeater のモデルとして JavaScript の配列を指定しています。
  • Repeater: 指定された model に基づいて、delegate を繰り返しインスタンス化するコンポーネントです。
  • property var myDataList: JavaScript の配列としてリストデータを定義しています。

利点

  • ListModel のような明示的なロールの定義が不要で、シンプルなデータの表示に適している。
  • より自由なレイアウトが可能 (Column, Row, Grid など、様々なレイアウトコンポーネント内で使用できる)。

欠点

  • 大量のデータを扱う場合には、ListModel とビューの最適化機能の方が効率的な場合がある。
  • ListView ほどの高度な機能(スクロール、選択など)は標準では提供されないため、必要に応じて自分で実装する必要がある。

Binding を使用したリストの作成

複雑なロジックに基づいて動的にリストを生成する場合、Binding を使用することができます。ただし、これは直接的なリストの「定義」というよりは、「生成」に近い概念です。

import QtQuick 2.15
import QtQuick.Controls 2.15

Rectangle {
    width: 200
    height: 150

    property int count: 3
    property var dynamicList: Binding {
        value: Array.from({ length: count }, (_, i) => "Item " + (i + 1))
    }

    Column {
        anchors.centerIn: parent
        spacing: 10

        Repeater {
            model: dynamicList
            delegate: Text { text: modelData }
        }
    }

    Button {
        text: "Increase Count"
        anchors.bottom: parent
        anchors.horizontalCenter: parent
        onClicked: {
            count++;
        }
    }
}

説明

  • "Increase Count" ボタンをクリックすると count が増加し、それに応じて dynamicList が再評価され、Repeater が更新されます。
  • Repeater: dynamicList をモデルとして使用し、要素を表示します。
  • Array.from({ length: count }, (_, i) => ...): 指定された長さの新しい配列を生成する JavaScript のメソッドです。
  • property var dynamicList: Binding { ... }: Binding を使用して dynamicList の値を動的に生成しています。ここでは、count プロパティに基づいて新しい JavaScript の配列を作成しています。
  • property int count: リストの要素数を制御するプロパティです。

利点

  • 複雑なリスト生成ロジックを QML 内で記述できる。
  • 他のプロパティの値に基づいてリストの内容を動的に生成できる。

欠点

  • データの変更をより細かく制御したい場合には、ListModel のメソッドを使用する方が適している。
  • 大規模なリストや頻繁な更新がある場合には、パフォーマンスに影響を与える可能性がある。

C++ 側のデータモデル (QAbstractListModel の派生クラス)

より複雑なデータ管理や、C++ 側のデータ構造と密接に連携する必要がある場合には、C++ で QAbstractListModel を継承したカスタムモデルを作成し、それを QML で使用する方法があります。これは、ListModel が提供する以上の柔軟性とパフォーマンスが必要な場合に有効です。

C++ コード (例 - ヘッダーファイル)

#include <QAbstractListModel>
#include <QVariant>
#include <QStringList>

class StringListModel : public QAbstractListModel
{
    Q_OBJECT
public:
    enum Roles {
        StringRole = Qt::UserRole + 1
    };

    StringListModel(const QStringList &strings, QObject *parent = nullptr);

    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    QHash<int, QByteArray> roleNames() const override;

private:
    QStringList m_strings;
};

C++ コード (例 - ソースファイル)

#include "stringlistmodel.h"

StringListModel::StringListModel(const QStringList &strings, QObject *parent)
    : QAbstractListModel(parent), m_strings(strings)
{
}

int StringListModel::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return 0;
    return m_strings.count();
}

QVariant StringListModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || index.row() < 0 || index.row() >= m_strings.count())
        return QVariant();

    if (role == Qt::DisplayRole || role == StringRole)
        return m_strings.at(index.row());
    return QVariant();
}

QHash<int, QByteArray> StringListModel::roleNames() const
{
    QHash<int, QByteArray> roles;
    roles[StringRole] = "displayString"; // QML で使用するロール名
    return roles;
}

QML コード

import QtQuick 2.15
import QtQuick.Controls 2.15

Rectangle {
    width: 200
    height: 150

    property StringListModel myCppModel: StringListModel {
        strings: ["From C++ 1", "From C++ 2", "From C++ 3"]
    }

    ListView {
        anchors.fill: parent
        model: myCppModel
        delegate: Text { text: displayString } // C++ で定義したロール名を使用
    }
}

説明

  • QML 側では、StringListModel のインスタンスを作成し、ListViewmodel として使用します。デリゲートでは、roleNames() で定義したロール名 (displayString) を使用してデータにアクセスします。
  • roleNames() で、QML からアクセスするためのロール名(ここでは "displayString")を定義しています。
  • rowCount(), data(), roleNames() などの仮想関数を実装して、モデルの基本的な振る舞いを定義します。
  • C++ で QAbstractListModel を継承した StringListModel を作成し、文字列のリストを管理しています。

利点

  • モデルの更新通知 (beginResetModel(), endResetModel(), beginInsertRows(), endInsertRows() など) を適切に実装することで、ビューを効率的に更新できる。
  • 大量のデータやパフォーマンスが重要な場合に、より効率的なデータ管理が可能。
  • C++ 側の複雑なデータ構造やロジックを QML から透過的に利用できる。
  • 単純なリスト表示であれば、ListModel の方が手軽に利用できる。
  • C++ の知識が必要となるため、開発の複雑性が増す。