C++ 타입의 어트리뷰트를 QML에 노출하기
QML은 C++ 코드에 정의된 기능으로 쉽게 확장할 수 있습니다. QML 엔진과 Qt 메타 객체 시스템의 긴밀한 통합으로 인해 QObject 파생 클래스 또는 Q_GADGET 유형에 의해 적절하게 노출된 모든 기능은 QML 코드에서 액세스할 수 있습니다. 따라서 C++ 데이터와 함수를 거의 또는 전혀 수정하지 않고도 QML에서 직접 액세스할 수 있습니다.
QML 엔진은 메타객체 시스템을 통해 QObject 인스턴스를 인트로스펙트할 수 있는 기능이 있습니다. 즉, 모든 QML 코드는 QObject 파생 클래스의 인스턴스에서 다음 멤버에 액세스할 수 있습니다:
- 속성
- 메서드(공개 슬롯이거나 Q_INVOKABLE 로 플래그가 지정된 경우)
- Signals
(또한 열거형은 Q_ENUM 로 선언된 경우 사용할 수 있습니다. 자세한 내용은 QML과 C++ 간의 데이터 유형 변환을 참조하세요).
일반적으로 이러한 신호는 QObject- 파생 클래스가 QML 유형 시스템에 등록되었는지 여부와 관계없이 QML에서 액세스할 수 있습니다. 그러나 클래스가 엔진에서 추가 유형 정보에 액세스해야 하는 방식으로 사용되는 경우(예: 클래스 자체를 메서드 파라미터 또는 속성으로 사용하거나 열거형 중 하나가 이러한 방식으로 사용되는 경우) 해당 클래스를 등록해야 할 수 있습니다. 컴파일 시 등록된 유형만 분석할 수 있으므로 QML에서 사용하는 모든 유형에 대해 등록하는 것이 좋습니다.
Q_GADGET 유형은 알려진 공통 기반에서 파생되지 않으며 자동으로 사용할 수 없으므로 등록이 필요합니다. 등록하지 않으면 해당 프로퍼티와 메서드에 액세스할 수 없습니다.
다른 모듈의 C++ 타입을 자신의 모듈에서 사용할 수 있도록 하려면 DEPENDENCIES 옵션을 사용하여 qt_add_qml_module 호출에 종속성을 추가하면 됩니다. 예를 들어, QML에 노출된 C++ 유형이 메서드 인수 및 반환 값으로 QColor 을 사용할 수 있도록 QtQuick 에 종속시킬 수 있습니다. QtQuick 은 값 유형 색상으로 QColor 을 노출합니다. 이러한 종속성은 런타임에 자동으로 추론될 수 있지만 이에 의존해서는 안 됩니다.
또한 이 문서에서 다루는 여러 가지 중요한 개념은 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()
메서드를 통해 쓸 수 있습니다:
참고: typedef를 사용하거나 Q_PROPERTY 타입에 사용하면 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
파일에 필수 속성으로 전달하여 사용할 수 있도록 할 수 있습니다:
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()에서 작성자가 변경될 때와 마찬가지로 - QML 엔진에 author
속성과 관련된 모든 바인딩을 업데이트해야 함을 알리고, 엔진은 Message::author()
을 다시 호출하여 text
속성을 업데이트합니다.
author
프로퍼티가 쓰기 가능하지만 관련 NOTIFY 신호가 없는 경우 text
값은 Message::author()
에서 반환한 초기 값으로 초기화되지만 이후 이 프로퍼티에 대한 변경 사항은 업데이트되지 않습니다. 또한 QML에서 프로퍼티에 바인딩을 시도하면 엔진에서 런타임 경고를 생성합니다.
참고: NOTIFY 신호의 이름은 <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
유형도 유형 시스템에 등록되어 있다면 MessageBody
을 Message
의 body
프로퍼티에 할당할 수 있으며, 이 모든 것이 QML 코드 내에서 가능합니다:
Message { body: MessageBody { text: "Hello, world!" } }
객체 목록 유형이 있는 속성
QObject 파생 유형의 목록을 포함하는 프로퍼티도 QML에 노출할 수 있습니다. 그러나 이를 위해서는 QList<T>가 아닌 QQmlListProperty 을 속성 유형으로 사용해야 합니다. QList 는 QObject 파생 유형이 아니므로 목록이 수정될 때 신호 알림과 같은 Qt 메타 객체 시스템을 통해 필요한 QML 프로퍼티 특성을 제공할 수 없기 때문입니다.
예를 들어, 아래 MessageBoard
클래스에는 Message
인스턴스 목록을 저장하는 QQmlListProperty 타입의 messages
프로퍼티가 있습니다:
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; };
메시지보드::메시지() 함수는 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 유형 시스템에 등록되어 있어야 합니다.
그룹화된 속성
모든 읽기 전용 객체 유형 속성은 QML 코드에서 그룹화된 속성으로 액세스할 수 있습니다. 이는 유형에 대한 속성 집합을 설명하는 관련 속성 그룹을 노출하는 데 사용할 수 있습니다.
예를 들어 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" }
그룹화된 프로퍼티로 노출되는 유형은 읽기 전용이며, 생성 시 부모 개체에 의해 유효한 값으로 초기화된다는 점에서 개체형 프로퍼티와 다릅니다. 그룹화된 속성의 하위 속성은 QML에서 수정할 수 있지만 그룹화된 속성 개체 자체는 변경되지 않는 반면, 개체형 속성은 언제든지 QML에서 새 개체 값을 할당할 수 있습니다. 따라서 그룹화된 속성 개체의 수명은 C++ 상위 구현에 의해 엄격하게 제어되는 반면, 객체 유형 속성은 QML 코드를 통해 자유롭게 생성 및 소멸할 수 있습니다.
메서드 노출(Qt 슬롯 포함)
QObject- 파생된 유형의 메서드는 QML 코드에서 액세스할 수 있습니다:
- Q_INVOKABLE() 매크로로 플래그가 지정된 공용 메서드
- 공용 Qt 슬롯인 메서드
예를 들어, 아래 MessageBoard
클래스에는 Q_INVOKABLE 매크로로 플래그가 지정된 postMessage()
메서드와 공용 슬롯인 refresh()
메서드가 있습니다:
class MessageBoard : public QObject { Q_OBJECT QML_ELEMENTpublic: Q_INVOKABLE bool postMessage(const QString &msg) { qDebug() << "Called the C++ method with" << msg; return true; }public slots: void refresh() { qDebug() << "Called the C++ slot"; } };
MessageBoard
의 인스턴스가 MyItem.qml
파일의 필수 속성으로 설정된 경우 MyItem.qml
는 아래 예시와 같이 두 메서드를 호출할 수 있습니다:
C++ | int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); MessageBoard msgBoard; QQuickView view; view.setInitialProperties({{"msgBoard", &msgBoard}}); view.setSource(QUrl::fromLocalFile("MyItem.qml")); view.show(); return app.exec(); } |
QML |
C++ 메서드에 QObject*
유형의 매개변수가 있는 경우, 해당 매개변수 값은 객체 id
또는 객체를 참조하는 JavaScript var 값을 사용하여 QML에서 전달할 수 있습니다.
QML은 오버로드된 C++ 함수의 호출을 지원합니다. 이름은 같지만 인수가 다른 C++ 함수가 여러 개 있는 경우 제공된 인수의 수와 유형에 따라 올바른 함수가 호출됩니다.
C++ 메서드에서 반환된 값은 QML의 JavaScript 표현식에서 액세스할 때 JavaScript 값으로 변환됩니다.
C++ 메서드와 'this' 객체
한 객체에서 C++ 메서드를 검색하여 다른 객체에서 호출하고 싶을 수 있습니다. 다음 예제인 Example
이라는 QML 모듈을 살펴봅시다:
C++ | |
QML | import QtQml import Example Invokable { objectName: "parent" property Invokable child: Invokable {} Component.onCompleted: child.invoke.call(this) } |
적절한 main.cpp에서 QML 코드를 로드하면 "부모에서 호출됨"이라고 인쇄되어야 합니다. 하지만 오랜 버그로 인해 그렇지 않습니다. 역사적으로 C++ 기반 메서드의 'this' 객체는 메서드와 불가분의 관계에 있습니다. 기존 코드에서 이 동작을 변경하면 'this' 객체가 여러 곳에 암시되어 있기 때문에 미묘한 오류가 발생할 수 있습니다. Qt 6.5부터는 명시적으로 올바른 동작을 선택해 C++ 메서드가 'this' 객체를 받아들이도록 허용할 수 있습니다. 이렇게 하려면 QML 문서에 다음 프래그마를 추가하세요:
pragma NativeMethodBehavior: AcceptThisObject
이 줄을 추가하면 위의 예제가 예상대로 작동합니다.
신호 노출
QObject- 파생된 유형의 모든 공개 신호는 QML 코드에서 액세스할 수 있습니다.
QML 엔진은 QML에서 사용되는 QObject 파생 유형의 모든 신호에 대한 신호 처리기를 자동으로 생성합니다. 신호 처리기의 이름은 항상 <신호>에 지정되며, <Signal>
는 신호의 이름이고 첫 글자는 대문자로 표시됩니다. 신호가 전달하는 모든 매개변수는 매개변수 이름을 통해 신호 핸들러에서 사용할 수 있습니다.
예를 들어 MessageBoard
클래스에 newMessagePosted()
신호와 단일 매개변수 subject
가 있다고 가정해 보겠습니다:
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 신호로 액세스할 수 있습니다. 이름은 같지만 매개변수가 다른 신호는 서로 구별할 수 없습니다.
© 2025 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.