C++型の属性をQMLに公開する
QMLは、C++コードで定義された機能によって容易に拡張できます。QMLエンジンとQtメタオブジェクトシステムが緊密に統合されているため、QObject 派生クラスまたはQ_GADGET 型によって適切に公開された機能は、QMLコードからアクセス可能です。 これにより、多くの場合、ほとんど、あるいはまったく変更を加えることなく、QMLからC++のデータや関数に直接アクセスできるようになります。
QMLエンジンは、メタオブジェクトシステムを通じてQObject インスタンスをイントロスペクトする機能を備えています。つまり、任意のQMLコードから、QObject を継承したクラスのインスタンスの以下のメンバにアクセスできます:
- プロパティ
- メソッド(パブリックスロットであるか、Q_INVOKABLE フラグが設定されている場合に限る)
- シグナル
(さらに、Q_ENUM で宣言されている場合、列挙型も利用可能です。詳細については、「QML と C++ 間のデータ型変換」を参照してください。)
一般的に、これらは、QObject を継承したクラスがQML 型システムに登録されているかどうかに関係なく、QML からアクセス可能です。 ただし、エンジンが追加の型情報にアクセスする必要がある方法でクラスを使用する場合(たとえば、クラス自体をメソッドのパラメータやプロパティとして使用する場合、またはその列挙型のいずれかをそのように使用する場合など)は、そのクラスを登録する必要があるかもしれません。 コンパイル時に解析できるのは登録済みの型のみであるため、QMLで使用するすべての型について登録を行うことを推奨します。
Q_GADGET 型は、既知の共通基底クラスから派生しておらず、自動的に利用可能にできないため、登録が必要です。登録を行わないと、それらのプロパティやメソッドにはアクセスできません。
DEPENDENCIESオプションを使用してqt_add_qml_module呼び出しに依存関係を追加することで、別のモジュールにある C++ 型を自分のモジュールで利用可能にすることができます。 たとえば、QMLから公開されるC++型がQColor をメソッドの引数や戻り値として使用できるようにするために、QtQuick に依存させたい場合などが考えられます。QtQuick は、QColor を 値 型colorとして公開しています。このような依存関係は実行時に自動的に推論される場合がありますが、これに依存すべきではありません。
また、このドキュメントで取り上げられている重要な概念の多くは、「C++ を使用した QML 拡張機能の作成」チュートリアルで実例が紹介されています。
C++ およびさまざまな QML 統合方法の詳細については、「C++ と QML の統合の概要」ページを参照してください。
データ型の取り扱いと所有権
C++からQMLへ転送されるデータ(プロパティ値、メソッドの引数や戻り値、シグナルの引数値など)は、すべてQMLエンジンでサポートされている型でなければなりません。
デフォルトでは、エンジンは多数の Qt C++ 型をサポートしており、QML から使用される際に適切に自動変換を行います。さらに、QML型システムに登録されたC++ クラスはデータ型として使用でき、適切に登録されていればその列挙型も同様に使用できます。詳細については、「Qml と C++ 間のデータ型変換」を参照してください。
また、C++ から QML へデータを転送する際には、データの所有権に関するルールが考慮されます。詳細については、「データの所有権」を参照してください。
プロパティの公開
Q_PROPERTY() マクロを使用すると、QObject を継承する任意のクラスに対してプロパティを指定できます。プロパティとは、読み取り関数と(オプションで)書き込み関数が関連付けられたクラスのデータメンバーのことです。
QObject から派生したクラス、またはQ_GADGET クラスのすべてのプロパティは、QML からアクセス可能です。
たとえば、以下はauthor プロパティを持つMessage クラスです。Q_PROPERTY マクロの呼び出しで指定されているとおり、このプロパティはauthor() メソッドを通じて読み取り可能であり、setAuthor() メソッドを通じて書き込み可能です。
注: Q_PROPERTY 型に対しては、typedefや usingを使用しないでください 。これらはmocを混乱させ、特定の型比較が失敗する原因となる可能性があります。
次のような記述の代わりに:
using FooEnum = Foo::Enum;
class Bar : public QObject
{
Q_OBJECT
Q_PROPERTY(FooEnum enum READ enum WRITE setEnum NOTIFY enumChanged)
};型を直接参照してください:
class Bar : public QObject
{
Q_OBJECT
Q_PROPERTY(Foo::Enum enum READ enum WRITE setEnum NOTIFY enumChanged)
};Message を利用可能にするには、C++ではQML_ELEMENT を、CMakeではqt_add_qml_moduleを使用する必要があります。
class Message : public QObject
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged)
public:
void setAuthor(const QString &a)
{
if (a != m_author) {
m_author = a;
emit authorChanged();
}
}
QString author() const
{
return m_author;
}
signals:
void authorChanged();
private:
QString m_author;
};Message のインスタンスを、MyItem.qml というファイルのrequiredプロパティとして渡すことで、それを利用可能にすることができます:
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQuickView view;
Message msg;
view.setInitialProperties({{"msg", &msg}});
view.setSource(QUrl::fromLocalFile("MyItem.qml"));
view.show();
return app.exec();
}その後、`MyItem.qml` から `author ` プロパティを読み取ることができます:
// MyItem.qml
import QtQuick
Text {
required property Message msg
width: 100; height: 100
text: msg.author // invokes Message::author() to get this value
Component.onCompleted: {
msg.author = "Jonah" // invokes Message::setAuthor()
}
}QMLとの相互運用性を最大限に高めるため、書き込み可能なプロパティには、プロパティの値が変更されるたびに発火する関連するNOTIFYシグナルを設定する必要があります。これにより、プロパティバインディングでの使用が可能になります。プロパティバインディングは、依存関係にあるプロパティのいずれかの値が変更された際に、関連するプロパティを自動的に更新することで、プロパティ間の関係を強制するQMLの重要な機能です。
上記の例では、author プロパティに関連付けられたNOTIFYシグナルは、Q_PROPERTY()マクロ呼び出しで指定されている通り、authorChanged です。 これは、このシグナルが発行されるたびに(Message::setAuthor() で author が変更されたときなど)、author プロパティを含むすべてのバインディングを更新する必要があることを QML エンジンに通知し、その結果、エンジンはMessage::author() を再度呼び出すことでtext プロパティを更新することを意味します。
もしauthor プロパティが書き込み可能であっても、関連するNOTIFYシグナルが設定されていなかった場合、text の値はMessage::author() によって返される初期値で初期化されますが、このプロパティに対するその後の変更によって更新されることはありません。さらに、QMLからこのプロパティにバインドしようとすると、エンジンから実行時警告が出力されます。
注: NOTIFYシグナルの名前は、<property> がプロパティ名である場合、<property>Changedとすることを推奨します 。QMLエンジンによって生成される関連するプロパティ変更シグナルハンドラは、関連するC++シグナルの名前にかかわらず、常にon<Property>Changed という形式をとるため、混乱を避けるためにシグナル名は本規約に従うことを推奨します。
Notifyシグナルの使用に関する注意事項
ループや過度な評価を防ぐため、開発者はプロパティ値が実際に変更された場合にのみ、プロパティ変更シグナルが発信されるようにする必要があります。また、プロパティまたはプロパティのグループが頻繁に使用されない場合は、複数のプロパティに対して同じ NOTIFY シグナルを使用することが許可されています。ただし、パフォーマンスが低下しないよう、慎重に行う必要があります。
NOTIFYシグナルの存在は、わずかなオーバーヘッドを伴います。プロパティの値がオブジェクトの構築時に設定され、その後変更されない場合があります。 この最も一般的な例は、サブオブジェクトを保持する読み取り専用プロパティであり、通常はグループ化されたプロパティ構文を通じてアクセスされます。この場合、サブオブジェクトは一度割り当てられ、所有者が削除されたときにのみ解放されます。このような場合、NOTIFYシグナルの代わりに、プロパティ宣言にCONSTANT属性を追加することができます。
CONSTANT属性は、その値がクラスのコンストラクタ内でのみ設定され、確定されるプロパティにのみ使用すべきです。バインディングで使用されることを意図するその他のすべてのプロパティには、代わりにNOTIFYシグナルを指定する必要があります。
オブジェクト型を持つプロパティ
オブジェクト型のプロパティは、そのオブジェクト型がQMLの型システムに適切に登録されていれば、QMLからアクセス可能です。
たとえば、Message 型には、MessageBody* 型のbody プロパティが定義されている場合があります:
class Message : public QObject
{
Q_OBJECT
Q_PROPERTY(MessageBody* body READ body WRITE setBody NOTIFY bodyChanged)
public:
MessageBody* body() const;
void setBody(MessageBody* body);
};
class MessageBody : public QObject
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE text NOTIFY textChanged)
// ...
}Message 型がQML型システムに登録されており、QMLコードからオブジェクト型として使用できると仮定します:
Message {
// ...
}もしMessageBody 型も型システムに登録されていれば、QMLコード内から直接、Message のbody プロパティにMessageBody を代入することが可能になります:
Message {
body: MessageBody {
text: "Hello, world!"
}
}オブジェクトリスト型を持つプロパティ
QObject 派生型のリストを含むプロパティも、QML に公開することができます。ただし、この目的では、プロパティ型としてQList<T> ではなく、QQmlListProperty を使用する必要があります。これは、QList がQObject 派生型ではないため、リストが変更された際のシグナル通知など、Qt メタオブジェクトシステムを通じて必要な QML プロパティの特性を提供できないからです。
たとえば、以下のMessageBoard クラスは、QQmlListProperty 型のmessages プロパティを持ち、Message インスタンスのリストを格納しています:
class MessageBoard : public QObject
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<Message> messages READ messages)
public:
QQmlListProperty<Message> messages();
private:
static void append_message(QQmlListProperty<Message> *list, Message *msg);
QList<Message *> m_messages;
};MessageBoard::messages()関数は、QList<T>m_messages メンバーからQQmlListProperty を単純に生成して返すだけであり、QQmlListProperty のコンストラクタで要求される適切なリスト変更関数を渡します:
QQmlListProperty<Message> MessageBoard::messages()
{
return QQmlListProperty<Message>(this, 0, &MessageBoard::append_message);
}
void MessageBoard::append_message(QQmlListProperty<Message> *list, Message *msg)
{
MessageBoard *msgBoard = qobject_cast<MessageBoard *>(list->object);
if (msg)
msgBoard->m_messages.append(msg);
}なお、QQmlListProperty のテンプレートクラスの型(この場合はMessage )は、QML型システムに登録されている必要があることに注意してください。
グループ化されたプロパティ
型自体にサブプロパティを持つプロパティは、その型がfont のような値型であるか、オブジェクト型であるかを問わず、グループ化されたプロパティの構文を使用して操作できます。グループ化されたプロパティは、ある型の属性セットを記述する関連するプロパティのグループを公開するのに役立ちます。
たとえば、Message::author プロパティが単純な文字列ではなく、MessageAuthor 型であり、name およびemail というサブプロパティを持つと仮定します。
class MessageAuthor : public QObject
{
Q_PROPERTY(QString name READ name WRITE setName)
Q_PROPERTY(QString email READ email WRITE setEmail)
public:
...
};
class Message : public QObject
{
Q_OBJECT
Q_PROPERTY(MessageAuthor* author READ author)
public:
Message(QObject *parent)
: QObject(parent), m_author(new MessageAuthor(this))
{
}
MessageAuthor *author() const {
return m_author;
}
private:
MessageAuthor *m_author;
};この場合、author プロパティには、QMLのグループ化されたプロパティ構文を使用して次のように書き込むことができます:
Message {
author.name: "Alexandra"
author.email: "alexandra@mail.com"
}author はオブジェクト型のプロパティであるため、グループ化された代入によってMessageAuthor オブジェクトが生成されることはありません。これらは、Message::author() が返すオブジェクトに書き込まれます。これにより、いくつかの影響が生じます:
- サブプロパティ名は、実行時にたまたまそこに格納されているオブジェクトの型ではなく、プロパティの宣言型(ここでは
MessageAuthor)に基づいて解決されます。Messageの異なるインスタンス化により、異なる型のauthorオブジェクトが生成される可能性があります。QML コンポーネントを作成する際に頼れるのは、宣言された型だけです。 - グループ化された代入が適用される時点で、オブジェクトは存在していなければなりません。そうでない場合、エンジンは「
Cannot set properties on author as it is null」のようなエラーをスローします。上記のように、所有者のコンストラクタ内でオブジェクトを作成するのがこれを保証する最も簡単な方法ですが、最初のアクセス時にオブジェクトを作成するゲッターを使用しても同様に機能します。 - オブジェクトのライフタイムは、C++の実装によって決定されます。グループ化されたプロパティ構文は、オブジェクトを作成も破棄もしません。
上記の例のように、author を読み取り専用として宣言することで、QML コードからMessageAuthor オブジェクトが置き換えられるのをさらに防ぐことができます。もしauthor にWRITE アクセサがあった場合、同様に新しいオブジェクトを割り当てることも可能です:
Message {
author: MessageAuthor {
name: "Alexandra"
email: "alexandra@mail.com"
}
}同じオブジェクト定義内で、同一のプロパティに対してこれら2つの形式を併用することはできません。author にオブジェクトを割り当て、かつ同じMessage 内でauthor.name も記述すると、エラーが発生します。 グループ化されたプロパティを別のスコープに配置することで、この制限を回避できます。ただし、そうすると、コンストラクタによって作成されたオブジェクトと明示的に代入されたオブジェクトの2つのMessageAuthor オブジェクトが存在することになり、結果が分かりにくくなります。グループ化されたプロパティがどちらのオブジェクトに適用されるかは、バインディングの評価順序によってのみ決定されます。
つまり、ベストプラクティスは以下の通りです:
- グループ化プロパティとして使用されるオブジェクトは読み取り専用にしておくこと。
- グループ自体の基底プロパティも操作する場合は、グループ化プロパティの構文の使用を避けてください。
font のような値型プロパティの場合、エンジンが値を読み取り、変更し、書き戻すため、グループ化された代入を行うには、そのプロパティが書き込み可能である必要があります。ただし、混乱を招く可能性は依然として問題となります。font 全体をその場で再代入し、その後別の場所でfont.bold プロパティを操作する場合、バインディングの評価順序によって、どちらが先に実行されるか、そしてその結果としてbold にどのような値が割り当てられるかが決まります。
メソッドの公開(Qtスロットを含む)
QObject を継承した型のメソッドは、以下の条件を満たす場合、QMLコードからアクセス可能です:
- Q_INVOKABLE() マクロで指定されたパブリックメソッド
- パブリックなQtスロットであるメソッド
たとえば、以下のMessageBoard クラスには、Q_INVOKABLE マクロで指定されたpostMessage() メソッドと、パブリックスロットであるrefresh() メソッドがあります:
classMessageBoard :publicQObject
{
Q_OBJECT
QML_ELEMENT
public:
Q_INVOKABLEboolpostMessage(constQString&msg) {
qDebug() << "Called the C++ method with" << msg;
return true;
}
public slots:
voidrefresh() {
qDebug() << "Called the C++ slot";
}
};MessageBoard のインスタンスが、ファイルMyItem.qml の必須プロパティとして設定されている場合、MyItem.qml は以下の例に示すように、2つのメソッドを呼び出すことができます:
| C++ | |
| QML |
C++ メソッドに `QObject* ` 型のパラメータがある場合、そのパラメータ値は、`id ` オブジェクト、またはそのオブジェクトを参照する JavaScript の `var ` 値を使用して、QML から渡すことができます。
QMLは、オーバーロードされたC++関数の呼び出しをサポートしています。同じ名前で引数が異なるC++関数が複数存在する場合、渡された引数の数と型に基づいて、適切な関数が呼び出されます。
C++メソッドから返された値は、QML内のJavaScript式からアクセスされる際に、JavaScriptの値に変換されます。
C++ メソッドと 'this' オブジェクト
あるオブジェクトからC++メソッドを取得し、別のオブジェクトに対してそのメソッドを呼び出したい場合があるでしょう。Example という名前のQMLモジュール内での以下の例を考えてみましょう:
| C++ | |
| QML | |
適切な main.cpp からこの QML コードを読み込むと、「invoked on parent」と出力されるはずです。しかし、長年のバグのため、実際には出力されません。従来、C++ ベースのメソッドの「this」オブジェクトは、そのメソッドと不可分に結びついていました。 既存のコードでこの挙動を変更すると、「this」オブジェクトが多くの箇所で暗黙的に使用されているため、見落としやすいエラーが発生する可能性があります。Qt 6.5 以降では、正しい挙動を明示的に有効にし、C++ メソッドが「this」オブジェクトを受け入れるように設定できます。これを行うには、Qml ドキュメントに次のプラグマを追加してください:
pragma NativeMethodBehavior: AcceptThisObjectこの行を追加することで、上記の例は期待通りに動作するようになります。
toString() のオーバーライド
toStringという名前の Q_INVOKABLE メソッド(引数なし)を定義すると、そのメソッドがJavaScriptのネイティブなtoString実装の代わりに、オブジェクトを文字列に変換するために使用されます。
シグナルの公開
QObject を継承した型のパブリックシグナルは、すべてQMLコードからアクセス可能です。
QMLエンジンでは、QMLから使用されるQObject 派生型のシグナルに対して、自動的にシグナルハンドラが生成されます。シグナルハンドラの名前は常にon<Signal>となり、<Signal> はシグナルの名前(先頭文字が大文字)です。シグナルから渡されるすべてのパラメータは、シグナルハンドラ内でパラメータ名を通じて利用可能です。
たとえば、MessageBoard クラスに、単一のパラメータsubject を持つnewMessagePosted() シグナルがあるとします。
class MessageBoard : public QObject
{
Q_OBJECT
public:
// ...
signals:
void newMessagePosted(const QString &subject);
};MessageBoard 型がQML型システムに登録されている場合、QMLで宣言されたMessageBoard オブジェクトは、onNewMessagePosted という名前のシグナルハンドラを使用してnewMessagePosted() シグナルを受信し、subject パラメータの値を確認することができます:
MessageBoard {
onNewMessagePosted: (subject)=> console.log("New message received:", subject)
}プロパティ値やメソッドのパラメータと同様に、シグナルのパラメータの型はQMLエンジンでサポートされているものでなければなりません。「QMLとC++間のデータ型変換」を参照してください。(未登録の型を使用してもエラーは発生しませんが、ハンドラからはそのパラメータ値にアクセスできません。)
クラスには同じ名前のシグナルが複数存在する場合がありますが、QMLシグナルとしてアクセスできるのは最後のシグナルのみです。なお、名前は同じでもパラメータが異なるシグナルは、互いに区別できないことに注意してください。
「 QML Type Registration Macros 」 および「C++ からの QML 型の定義」も参照してください 。
© 2026 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.