C++에서 QML 객체와 상호 작용하기
모든 QML 객체 유형은 엔진에서 내부적으로 구현되었든 타사 소스에서 정의되었든 상관없이 QObject- 파생 유형입니다. 즉, QML 엔진은 Qt 메타 객체 시스템을 사용하여 모든 QML 객체 유형을 동적으로 인스턴스화하고 생성된 객체를 검사할 수 있습니다.
이는 시각적으로 렌더링할 수 있는 QML 객체를 표시하거나 시각적이지 않은 QML 객체 데이터를 C++ 애플리케이션에 통합하기 위해 C++ 코드에서 QML 객체를 생성할 때 유용합니다. QML 객체가 생성되면 C++에서 검사하여 프로퍼티를 읽고 쓰고, 메서드를 호출하고, 신호 알림을 수신할 수 있습니다.
C++ 및 다양한 QML 통합 방법에 대한 자세한 내용은 C++ 및 QML 통합 개요 페이지를 참조하세요.
C++에서 QML 개체 로드하기
QML 문서는 QQmlComponent 또는 QQuickView 을 사용하여 로드할 수 있습니다. QQmlComponent 은 QML 문서를 C++ 개체로 로드한 다음 C++ 코드에서 수정할 수 있습니다. QQuickView 도 이 작업을 수행하지만 QQuickView 은 QWindow 파생 클래스이므로 로드된 개체는 시각적 디스플레이로도 렌더링됩니다. QQuickView 은 일반적으로 표시 가능한 QML 개체를 애플리케이션의 사용자 인터페이스에 통합하는 데 사용됩니다.
예를 들어 다음과 같은 MyItem.qml
파일이 있다고 가정해 보겠습니다:
import QtQuick Item { width: 100; height: 100 }
이 QML 문서는 다음 C++ 코드를 사용하여 QQmlComponent 또는 QQuickView 으로 로드할 수 있습니다. QQmlComponent 를 사용하면 QQmlComponent::create()를 호출하여 컴포넌트의 새 인스턴스를 생성해야 하지만, QQuickView 를 사용하면 QQuickView::rootObject()를 통해 액세스할 수 있는 컴포넌트의 인스턴스가 자동으로 생성됩니다:
// Using QQmlComponent QQmlEngine engine; QQmlComponent component(&engine, QUrl::fromLocalFile("MyItem.qml")); QObject *object = component.create(); ... delete object; | // Using QQuickView QQuickView view; view.setSource(QUrl::fromLocalFile("MyItem.qml")); view.show(); QObject *object = view.rootObject(); |
이 object
은 생성된 MyItem.qml
컴포넌트의 인스턴스입니다. 이제 QObject::setProperty() 또는 QQmlProperty::write()를 사용하여 항목의 속성을 수정할 수 있습니다:
object->setProperty("width", 500); QQmlProperty(object, "width").write(500);
QObject::setProperty()
와 QQmlProperty::write()
의 차이점은 후자는 속성 값을 설정하는 것 외에 바인딩도 제거한다는 것입니다. 예를 들어 위의 width
할당이 height
에 대한 바인딩이었다고 가정해 보겠습니다:
width: height
object->setProperty("width", 500)
호출 후 Item
의 height
이 변경되면 바인딩이 활성 상태로 유지되므로 width
이 다시 업데이트됩니다. 그러나 QQmlProperty(object, "width").write(500)
호출 후에 height
가 변경되면 바인딩이 더 이상 존재하지 않으므로 width
는 변경되지 않습니다.
또는 객체를 실제 유형으로 형변환하고 컴파일 타임 안전성을 갖춘 메서드를 호출할 수 있습니다. 이 경우 MyItem.qml
의 기본 객체는 QQuickItem 클래스에 의해 정의된 Item 입니다:
QQuickItem *item = qobject_cast<QQuickItem*>(object); item->setWidth(500);
QMetaObject::invokeMethod() 및 QObject::connect()을 사용하여 컴포넌트에 정의된 모든 신호에 연결하거나 메서드를 호출할 수도 있습니다. 자세한 내용은 아래의 QML 메서드 호출 및 QML 신호에 연결하기를 참조하세요.
잘 정의된 C++ 인터페이스를 통해 QML 객체에 액세스하기
C++에서 QML과 상호 작용하는 가장 좋은 방법은 C++에서 이를 위한 인터페이스를 정의하고 QML 자체에서 액세스하는 것입니다. 다른 방법을 사용하면 QML 코드를 리팩터링할 때 QML/C++ 상호 작용이 쉽게 중단될 수 있습니다. 또한 QML을 통해 코드를 구동하면 사용자와 qmllint와 같은 도구 모두 더 쉽게 추론할 수 있으므로 QML과 C++ 코드의 상호 작용을 추론하는 데 도움이 됩니다. C++에서 QML에 액세스하면 외부 C++ 코드가 특정 QML 구성 요소를 수정하지 않는지 수동으로 확인하지 않고는 이해할 수 없는 QML 코드가 생성되며, 시간이 지나면서 액세스 범위가 변경될 수 있으므로 이 전략을 계속 사용하면 유지 관리에 부담이 될 수 있습니다.
QML이 상호 작용을 주도하도록 하려면 먼저 C++ 인터페이스를 정의해야 합니다:
class CppInterface : public QObject { Q_OBJECT QML_ELEMENT // ... };
QML 기반 접근 방식을 사용하면 이 인터페이스는 두 가지 방식으로 상호 작용할 수 있습니다:
싱글톤
한 가지 옵션은 인터페이스에 QML_SINGLETON 매크로를 추가하여 인터페이스를 싱글톤으로 등록하고 모든 컴포넌트에 노출하는 것입니다. 그런 다음 간단한 import 문을 통해 인터페이스를 사용할 수 있습니다:
import my.company.module Item { Component.onCompleted: { CppInterface.foo(); } }
단순히 객체를 전달하려면 프로퍼티를 통해 다른 컴포넌트에 명시적으로 전달하거나 자격이 없는 액세스를 사용하는 느리고 권장되지 않는 방법을 사용해야 하므로 루트 컴포넌트보다 더 많은 곳에서 인터페이스가 필요한 경우 이 방법을 사용하세요.
초기 프로퍼티
또 다른 옵션은 QML_UNCREATABLE 을 통해 인터페이스를 생성할 수 없는 것으로 표시하고 QQmlComponent::createWithInitialProperties() 및 QML 끝에 필요한 프로퍼티를 사용하여 루트 QML 컴포넌트에 제공하는 것입니다.
루트 컴포넌트는 다음과 같이 보일 수 있습니다:
import QtQuick Item { required property CppInterface interface Component.onCompleted: { interface.foo(); } }
여기서 속성을 필수로 표시하면 인터페이스 속성이 설정되지 않은 상태에서 컴포넌트가 생성되지 않도록 보호할 수 있습니다.
그런 다음 createWithInitialProperties()
을 사용하는 것을 제외하고는 C++에서 QML 객체 로딩에 설명된 것과 동일한 방식으로 컴포넌트를 초기화할 수 있습니다:
component.createWithInitialProperties(QVariantMap{{u"interface"_s, QVariant::fromValue<CppInterface *>(new CppInterface)}});
이 방법은 인터페이스를 루트 컴포넌트에서만 사용할 수 있어야 하는 경우 선호됩니다. 또한 C++ 측에서 인터페이스의 신호 및 슬롯에 더 쉽게 연결할 수 있습니다.
이 두 가지 방법 중 어느 것도 사용자의 요구에 적합하지 않은 경우 대신 C++ 모델의 사용법을 조사할 수 있습니다.
오브젝트 이름으로 로드된 QML 오브젝트에 액세스하기
QML 컴포넌트는 기본적으로 형제자매와 자신의 자식을 가진 객체 트리입니다. QML 컴포넌트의 자식 개체는 QObject::findChild()와 함께 QObject::objectName 속성을 사용하여 찾을 수 있습니다. 예를 들어 MyItem.qml
의 루트 항목에 자식 Rectangle 항목이 있는 경우입니다:
import QtQuick Item { width: 100; height: 100 Rectangle { anchors.fill: parent objectName: "rect" } }
자식은 다음과 같이 찾을 수 있습니다:
한 객체에는 동일한 objectName
을 가진 자식이 여러 개 있을 수 있습니다. 예를 들어 ListView 는 델리게이트의 여러 인스턴스를 생성하므로 델리게이트가 특정 objectName 으로 선언된 경우 ListView 에는 동일한 objectName
을 가진 여러 자식이 있습니다. 이 경우 QObject::findChildren()를 사용하여 일치하는 objectName
을 가진 모든 자식을 찾을 수 있습니다.
경고: C++에서 QML 개체에 액세스하여 조작할 수는 있지만 테스트 및 프로토타이핑 목적을 제외하고는 권장되지 않는 방법입니다. QML과 C++ 통합의 강점 중 하나는 C++ 로직 및 데이터세트 백엔드와 별도로 QML에서 UI를 구현할 수 있다는 점인데, C++ 측에서 QML을 직접 조작하기 시작하면 이 기능은 실패합니다. 이러한 접근 방식은 또한 C++에 영향을 주지 않으면서 QML UI를 변경하는 것을 어렵게 만듭니다.
C++에서 QML 객체 유형의 멤버에 액세스하기
Properties
QML 객체에 선언된 모든 프로퍼티는 C++에서 자동으로 액세스할 수 있습니다. 다음과 같은 QML 항목이 있다고 가정해 보겠습니다:
someNumber
속성의 값은 QQmlProperty 또는 QObject::setProperty() 및 QObject::property()을 사용하여 설정하고 읽을 수 있습니다:
QQmlEngine 엔진;QQmlComponent component(&engine, "MyItem.qml");QObject *object = component.create(); qDebug() << "Property value:" << QQmlProperty::read(object, "someNumber").toInt(); QQmlProperty::write(object, "someNumber", 5000); qDebug() << "Property value:" << object->property("someNumber").toInt(); object->setProperty("someNumber", 100);
QML 속성 값을 변경할 때는 항상 QObject::setProperty(), QQmlProperty 또는 QMetaProperty::write()를 사용하여 QML 엔진이 속성 변경을 인식하도록 해야 합니다. 예를 들어 내부적으로 m_buttonText
멤버 변수의 값을 반영하는 buttonText
속성이 있는 사용자 지정 유형 PushButton
이 있다고 가정해 보겠습니다. 이렇게 멤버 변수를 직접 수정하는 것은 좋은 생각이 아닙니다:
//bad code QQmlComponent component(engine, "MyButton.qml"); PushButton *button = qobject_cast<PushButton*>(component.create()); button->m_buttonText = "Click me";
값이 직접 변경되기 때문에 Qt의 메타 객체 시스템을 우회하고 QML 엔진은 속성 변경을 인식하지 못하기 때문입니다. 즉, buttonText
에 대한 속성 바인딩이 업데이트되지 않고 onButtonTextChanged
핸들러도 호출되지 않습니다.
QML 메서드 호출
모든 QML 메서드는 메타객체 시스템에 노출되어 있으며 C++에서 QMetaObject::invokeMethod()를 사용하여 호출할 수 있습니다. 아래 코드 스니펫에 표시된 것처럼 콜론 문자 뒤에 매개변수의 유형과 반환값을 지정할 수 있습니다. 예를 들어 특정 서명이 있는 C++의 신호를 QML 정의 메서드에 연결하려는 경우 유용할 수 있습니다. 유형을 생략하면 C++ 시그니처는 QVariant 을 사용합니다.
다음은 QMetaObject::invokeMethod()를 사용하여 QML 메서드를 호출하는 C++ 애플리케이션입니다:
QML | // MyItem.qml import QtQuick Item { function myQmlFunction(msg: string) : string { console.log("Got message:", msg) return "some return value" } } |
C++ | // main.cppQQmlEngine engine;QQmlComponent component(&engine, "MyItem.qml");QObject *object = component.create();QString 반환값;QString msg = "Hello from C++";QMetaObject::invokeMethod(object, "myQmlFunction",Q_RETURN_ARG(QString, returnedValue),Q_ARG(QString, msg)); qDebug() << "QML function returned:" << returnedValue; delete object; |
콜론 뒤에 지정된 매개변수와 반환 유형에 주목하세요. 값 유형과 객체 유형을 유형 이름으로 사용할 수 있습니다.
QML에서 유형이 생략되거나 var
로 지정된 경우 QMetaObject::invokeMethod 를 호출할 때 Q_RETURN_ARG() 및 Q_ARG()와 함께 QVariant 을 유형으로 전달해야 합니다.
QML 신호에 연결하기
모든 QML 신호는 C++에서 자동으로 사용할 수 있으며, 일반적인 Qt C++ 신호와 마찬가지로 QObject::connect()를 사용하여 연결할 수 있습니다. 그 대신 신호 핸들러를 사용하여 모든 C++ 신호를 QML 객체가 수신할 수 있습니다.
다음은 문자열형 파라미터와 함께 qmlSignal
라는 이름의 신호가 방출되는 QML 컴포넌트입니다. 이 신호는 QObject::connect()를 사용하여 C++ 객체의 슬롯에 연결되므로 qmlSignal
가 방출될 때마다 cppSlot()
메서드가 호출됩니다:
class MyClass : public QObject { Q_OBJECT공용 슬롯: void cppSlot(const QString &msg) { qDebug() << "Called the C++ slot with message:" << msg; } };int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQuickView view(QUrl::fromLocalFile("MyItem.qml")); QObject *item = view.rootObject(); MyClass myClass; QObject::connect(item, SIGNAL(qmlSignal(QString)), &myClass, SLOT(cppSlot(QString))); view.show(); return app.exec(); } |
신호 매개변수의 QML 객체 유형은 C++에서 클래스에 대한 포인터로 변환됩니다:
class MyClass : public QObject { Q_OBJECT공용 슬롯: void cppSlot(QQuickItem *item) { qDebug() << "Called the C++ slot with item:" << item; qDebug() << "Item dimensions:" << item->width() << item->height(); } };int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQuickView view(QUrl::fromLocalFile("MyItem.qml")); QObject *item = view.rootObject(); MyClass myClass; QObject::connect(item, SIGNAL(qmlSignal(QVariant)), &myClass, SLOT(cppSlot(QVariant))); view.show(); return app.exec(); } |
© 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.