QMLとC++間のデータ型変換

QMLとC++の間でデータのやり取りが行われる場合、QMLエンジンはQMLやC++で使 うのに適したデータ型に変換します。このため、交換されるデータはエンジンが認識できる型である必要があります。

QML エンジンでは、多数の Qt C++ データ型をビルトインでサポートしています。さらに、カスタムの C++ 型を QML の型システムに登録し、エンジンで利用できるようにすることもできます。

C++ と様々な QML インテグレーションの方法についての詳細は、C++ と QML インテグレーションの概要のページを参照してください。

このページでは、QMLエンジンがサポートするデータ型と、それらがQMLとC++の間でどのように変換されるかについて説明します。

データの所有権

C++からQMLへデータを転送する場合、データの所有権は常にC++にあります。ただし、C++の明示的なメソッド呼び出しからQObject が返された場合は例外です。この場合、QQmlEngine::CppOwnership を指定してQQmlEngine::setObjectOwnership() を呼び出すことで、オブジェクトの所有権が明示的に C++ に残るように設定されていない限り、QML エンジンがオブジェクトの所有権を持ちます。

また、QMLエンジンはQt C++オブジェクトの通常のQObject 親の所有権を尊重し、親を持つQObject インスタンスを削除することはありません。

基本的な Qt データ型

デフォルトでは、QML は以下の Qt データ型を認識します。これらのデータ型は、C++ から QML に渡される際、またその逆の場合にも、対応するQML のデータ型に自動的に変換されます:

注意: Qt GUIモジュールが提供するクラス(QColorQFontQQuaternionQMatrix4x4 など)は、Qt Quickモジュールが含まれている場合のみ QML から利用可能です。

便宜上、これらの型の多くはQMLの中で文字列値や、QtQml::Qt オブジェクトが提供する関連メソッドで指定することができます。例えば、Image::sourceSize プロパティはsize 型(これは自動的にQSize 型に変換されます)であり、"widthxheight "としてフォーマットされた文字列値、または Qt.size() 関数によって指定することができます:

Item {
    Image { sourceSize: "100x200" }
    Image { sourceSize: Qt.size(100, 200) }
}

詳しくはQML Value Typesのドキュメントを参照してください。

QObject派生型

QObject から派生したクラスは、QML の型システムに登録されていれば、QML と C++ 間でデータ交換を行うための型として使用することができます。

QML ではインスタンス化可能な型とインスタンス化不可能な型の両方を登録することが できます。一旦 QML の型として登録されたクラスは、QML と C++ の間でデータ交換を行うためのデータ型として使用することができます。型登録の詳細についてはC++ 型を QML 型システムに登録するを参照してください。

Qt と JavaScript の型変換

QML エンジンには、QML と C++ 間でデータをやり取りする際に、いくつかの Qt 型を関連する JavaScript 型に変換する機能が組み込まれています。これにより、データ値やその属性へのアクセスを提供するカスタム型を実装する必要なく、これらの型を使用し、C++やJavaScriptで受け取ることが可能になります。

(なお、QMLのJavaScript環境では、StringDateNumber などのネイティブJavaScriptのオブジェクトプロトタイプを変更し、追加機能を提供しています。詳しくはJavaScriptホスト環境を参照してください)。

QVariantList と QVariantMap から JavaScript の配列とオブジェクトへ

QML エンジンは、QVariantList と JavaScript の配列間、QVariantMap と JavaScript のオブジェクト間の自動型変換を提供します。

例えば、以下の QML で定義された関数は、2つの引数、配列とオブジェクトを受け取り、 配列とオブジェクトの項目にアクセスするための標準的な JavaScript の構文を使って、 その内容を表示します。以下の C++ コードはこの関数を呼び出します。QVariantListQVariantMap を渡しますが、それぞれ自動的に JavaScript の配列とオブジェクトの値に変換されます:

QML
// MyItem.qml
Item {
    function readValues(anArray, anObject) {
        for (var i=0; i<anArray.length; i++)
            console.log("Array item:", anArray[i])

        for (var prop in anObject) {
            console.log("Object item:", prop, "=", anObject[prop])
        }
    }
}
C++
// C++
QQuickView view(QUrl::fromLocalFile("MyItem.qml"));

QVariantList list;
list << 10 << QColor(Qt::green) << "bottles";

QVariantMap map;
map.insert("language", "QML");
map.insert("released", QDate(2010, 9, 21));

QMetaObject::invokeMethod(view.rootObject(), "readValues",
        Q_ARG(QVariant, QVariant::fromValue(list)),
        Q_ARG(QVariant, QVariant::fromValue(map)));

のような出力が得られます:

Array item: 10
Array item: #00ff00
Array item: bottles
Object item: language = QML
Object item: released = Tue Sep 21 2010 00:00:00 GMT+1000 (EST)

同様に、C++の型がプロパティ型やメソッドのパラメータにQVariantListQVariantMap 型を使用している場合、その値はJavaScriptの配列やオブジェクトとしてQMLで作成することができ、C++に渡される際には自動的にQVariantListQVariantMap に変換されます。

C++ 型のQVariantListQVariantMap プロパティは値として保存され、QML のコードで変更することはできません。マップやリスト全体を置き換えることはできますが、その中身を操作することはできません。次のコードはl のプロパティがQVariantList の場合は動作しません:

MyListExposingItem {
   l: [1, 2, 3]
   Component.onCompleted: l[0] = 10
}

次のコードは動作します:

MyListExposingItem {
   l: [1, 2, 3]
   Component.onCompleted: l = [10, 2, 3]
}

QDateTimeからJavaScriptのDateへ

QML エンジンは、QDateTime の値と JavaScriptDate オブジェクトとの自動型変換機能を提供します。

例えば、以下のQMLで定義された関数は、JavaScriptのDate オブジェクトを受け取り、また、現在の日付と時刻を持つ新しいDate オブジェクトを返します。以下の C++ コードはこの関数を呼び出し、QDateTime の値を渡します。 の値は、readDate() 関数に渡されるときに、エンジンによって自動的にDate オブジェクトに変換されます。一方、readDate()関数は、Date オブジェクトを返します。このオブジェクトは、C++ で受け取ったときに自動的にQDateTime 値に変換されます:

QML
// MyItem.qml
Item {
    function readDate(dt) {
        console.log("The given date is:", dt.toUTCString());
        return new Date();
    }
}
C++
// C++
QQuickView view(QUrl::fromLocalFile("MyItem.qml"));

QDateTime dateTime = QDateTime::currentDateTime();
QDateTime retValue;

QMetaObject::invokeMethod(view.rootObject(), "readDate",
        Q_RETURN_ARG(QVariant, retValue),
        Q_ARG(QVariant, QVariant::fromValue(dateTime)));

qDebug() << "Value returned from readDate():" << retValue;

同様に、C++の型がプロパティ型やメソッドのパラメータにQDateTime を使用している場合、その値はJavaScriptのDate オブジェクトとしてQMLで作成することができ、C++に渡される際に自動的にQDateTime の値に変換されます。

注意: 月番号の違いに注意してください:JavaScriptでは1月を0から11までとし、Qtでは1月を1から12までとします。

注意: JavaScriptで文字列をDate オブジェクトの値として使用する場合、時間フィールドを持たない文字列(つまり単純な日付)は、関連する日のUTC開始時刻として解釈されることに注意してください。new Date(y, m, d) ではその日のローカル時刻の開始時刻が使用されます。JavaScriptでDate オブジェクトを作成する他のほとんどの方法は、名前にUTCを含むメソッドが使用されない限り、ローカル時刻を生成します。getHours()あなたのプログラムがUTCより後ろのゾーン(公称では本初子午線より西)で実行される場合、日付のみの文字列を使用すると、Date オブジェクトが生成され、getDate() は文字列の日数より1つ少なくなります。これらのメソッドのUTCバリアントであるgetUTCDate()getUTCHours() は、そのようなDate オブジェクトに対して期待する結果を与えます。次のセクションも参照してください。

QDate と JavaScript Date

QMLエンジンは、QDate とJavaScriptのDate の間で、日付をUTCの開始日で表すことにより、自動的に変換します。日付はQDateTime を経由してdate() メソッドを選択することでQDate にマップされ、UTC形式が次の日の始まりと重ならない限り、ローカルタイム形式が使われます。

この少々風変わりな配置は、JavaScriptが日付のみの文字列からDate オブジェクトを構築する場合、その日のUTC開始時刻を使用しますが、new Date(y, m, d) では、前のセクションの最後の注釈で説明したように、指示された日付のローカルタイム開始時刻を使用するという事実を回避するためのものです。

その結果、QDate のプロパティやパラメータがQMLに公開されている場合、その値を読み取る際には注意が必要です。Date.getUTCFullYear()Date.getUTCMonth()Date.getUTCDate() のメソッドは、その名前にUTCが含まれていない対応するメソッドよりも、ユーザーが期待する結果を提供する可能性が高くなります。

そのため、QDateTime プロパティを使用する方がより堅牢であることが一般的です。これにより、QDateTime 側で、日付(と時刻)が UTC で指定されているか、ローカルタイムで指定されているかを制御することができます。JavaScript コードが同じ基準で動作するように書かれている限り、トラブルを避けることができるはずです。

QTimeとJavaScriptの日付

QMLエンジンはQTime の値から JavaScriptDate オブジェクトへの自動型変換を行います。QTime の値には日付要素が含まれていないため、変換のためだけに日付要素が作成されます。従って、結果の Date オブジェクトの日付成分に依存してはいけません。

JavaScriptのDate オブジェクトからQTime への変換は、QDateTime オブジェクト(ローカル時間を使用)に変換し、そのtime ()メソッドを呼び出すことで行われます。

シーケンス型からJavaScriptの配列へ

シーケンス型の一般的な説明についてはQML シーケンス型を参照してください。QtQml module には使いたいシーケンス型がいくつか含まれています。

また、QJSEngine::newArray() を使ってQJSValue を構築することで、リストのようなデータ構造を作ることもできます。このようなJavaScriptの配列は、QMLとC++の間で受け渡しする際に変換を必要としません。C++ から JavaScript 配列を操作する方法についてはQJSValue#Working With Arrays を参照してください。

QByteArray から JavaScript ArrayBuffer への変換

QML エンジンはQByteArray の値と JavaScriptArrayBuffer オブジェクトとの自動型変換機能を提供します。

値の型

QPoint のような Qt のいくつかの値型は、JavaScript では C++ API と同じプロパティや関数を持つオブジェクトとして表現されます。C++ のカスタム値型でも同じ表現が可能です。QMLエンジンでカスタム値型を有効にするには、クラス宣言にQ_GADGET のアノテーションを付ける必要があります。JavaScriptの表現で表示されるプロパティは、Q_PROPERTY で宣言する必要があります。同様に、関数はQ_INVOKABLE でマークする必要があります。これはQObject ベースの C++ API でも同じです。例えば、以下のActor クラスはガジェットとしてアノテーションされ、プロパティを持っています:

class Actor
{
    Q_GADGET
    Q_PROPERTY(QString name READ name WRITE setName)
public:
    QString name() const { return m_name; }
    void setName(const QString &name) { m_name = name; }

private:
    QString m_name;
};

Q_DECLARE_METATYPE(Actor)

通常のパターンは、プロパティの型としてガジェットクラスを使用するか、シグナルの引数としてガジェットを発することです。このような場合、ガジェットのインスタンスはC++とQMLの間で値として渡されます(値型であるため)。QMLのコードがガジェットのプロパティを変更した場合、ガジェット全体が再作成され、C++のプロパティ・セッターに引き渡されます。Qt 5では、ガジェット型をQMLで直接宣言してインスタンス化することはできません。対照的に、QObject インスタンスは宣言することができます。QObject インスタンスは常に C++ から QML へポインタによって渡されます。

列挙型

カスタムの列挙型をデータ型として使用するには、そのクラスを登録し、さらに Qt のメタオブジェクトシステムに登録するために、Q_ENUM() で列挙型を宣言する必要があります。例えば、以下のMessage クラスはStatus 列挙型を持っています:

class Message : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Status status READ status NOTIFY statusChanged)
public:
    enum Status {
        Ready,
        Loading,
        Error
    };
    Q_ENUM(Status)
    Status status() const;
signals:
    void statusChanged();
};

Message クラスが QML の型システムに登録されていれば、Status enum を QML から利用することができます:

Message {
     onStatusChanged: {
         if (status == Message.Ready)
             console.log("Message is loaded!")
     }
 }

Q_FLAGQMLでenumをflags

注意: QMLからアクセスするためには、enumの値の名前は大文字で始まらなけれ ばなりません。

...
enum class Status {
          Ready,
          Loading,
          Error
}
Q_ENUM(Status)
...

enumクラスはスコープされたプロパティとスコープされていないプロパティとしてQMLに登録されます。Ready の値はMessage.Status.ReadyMessage.Ready に登録されます。

enum クラスを使用する場合、同じ識別子を使用する複数の enum が存在する可能性があります。スコープされていない登録は、最後に登録された列挙型で上書きされます。このような名前の衝突を含むクラスでは、特別なQ_CLASSINFO マクロでクラスに注釈を付けることで、スコープなし登録を無効にすることができます。スコープされた列挙が同じ名前空間にマージされないようにするには、RegisterEnumClassesUnscoped という名前とfalse という値を使用します。

class Message : public QObject
    {
        Q_OBJECT
        Q_CLASSINFO("RegisterEnumClassesUnscoped", "false")
        Q_ENUM(ScopedEnum)
        Q_ENUM(OtherValue)

    public:
        enum class ScopedEnum {
              Value1,
              Value2,
              OtherValue
        };
        enum class OtherValue {
              Value1,
              Value2
        };
    };

関連する型の列挙型は通常、関連する型のスコープに登録されます。例えば、Q_PROPERTY の宣言で異なる型の列挙型を使用すると、その型のすべての列挙型がQMLで使用できるようになります。このような現象は、機能であるというよりも、むしろ問題です。これを防ぐためには、Q_CLASSINFO という特別なマクロでクラスのアノテーションを記述してください。この型に関連する型の列挙型を登録できないようにするには、RegisterEnumsFromRelatedTypes という名前にfalse という値をつけます。

QMLで使用する列挙型は、他の型に注入される列挙型に依存するのではなく、QML_ELEMENTQML_NAMED_ELEMENT を使用して、明示的に登録する必要があります。

class OtherType : public QObject
{
    Q_OBJECT
    QML_ELEMENT

public:
    enum SomeEnum { A, B, C };
    Q_ENUM(SomeEnum)

    enum AnotherEnum { D, E, F };
    Q_ENUM(AnotherEnum)
};

class Message : public QObject
{
    Q_OBJECT
    QML_ELEMENT

    // This would usually cause all enums from OtherType to be registered
    // as members of Message ...
    Q_PROPERTY(OtherType::SomeEnum someEnum READ someEnum CONSTANT)

    // ... but this way it doesn't.
    Q_CLASSINFO("RegisterEnumsFromRelatedTypes", "false")

public:
    OtherType::SomeEnum someEnum() const { return OtherType::B; }
};

重要な違いは、QMLにおける列挙型のスコープです。関連するクラスの enum が自動的に登録される場合、そのスコープはインポートされた型になります。上記の場合、Q_CLASSINFO がなければ、Message.A のようになります。列挙型を保持するC++型が明示的に登録され、関連する型からの列挙型の登録が抑制されている場合、列挙型を保持するC++型のQML型は、そのすべての列挙型のスコープとなります。QMLではMessage.A の代わりにOtherType.A を使うことになります。

QML_FOREIGN を使って変更できない型を登録することもできます。また、同じ C++ 型が QML の値型として登録されている場合でも、QML_FOREIGN_NAMESPACE を使って C++ 型の列挙型を任意の大文字の名前の QML 名前空間に登録することができます。

シグナルやメソッドのパラメータとしての列挙型

列挙型のパラメータを持つ C++ シグナルやメソッドは、列挙型とシグナルやメソッドが同じクラス内で 宣言されているか、列挙型の値がQt Namespace で宣言されているものであれば、 QML から使用することができます。

さらに、列挙型のパラメータを持つ C++ シグナルをconnect()関数を使って QML の関数に接続する場合、qRegisterMetaType() を使って列挙型を登録する必要があります。

QML シグナルでは、enum 型の値はint を用いてシグナルパラメータとして渡すことができます:

Message {
    signal someOtherSignal(int statusValue)

    Component.onCompleted: {
        someOtherSignal(Message.Loading)
    }
}

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