이 페이지에서

C++에서 QML 객체와 상호작용하기

모든 QML 객체 유형은 엔진에 의해 내부적으로 구현되었든 타사 소스에 의해 정의되었든 상관없이 ` QObject`에서 파생된 유형입니다. 즉, QML 엔진은 Qt 메타 객체 시스템 (Qt Meta Object System )을 사용하여 모든 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 의 기본 객체는 Item 이며, 이는 QQuickItem 클래스에 의해 정의됩니다:

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 `를 통해 인터페이스를 생성 불가능(uncreatable)으로 표시하고, ` QQmlComponent::createWithInitialProperties()`를 사용하여 루트 QML 컴포넌트에 전달하며, QML 측에서 필수 속성을 지정하는 것입니다.

루트 컴포넌트는 다음과 같이 보일 수 있습니다:

import QtQuick

Item {
    required property CppInterface interface
    Component.onCompleted: {
        interface.foo();
    }
}

여기서 속성을 필수로 지정하면 인터페이스 속성이 설정되지 않은 상태에서 컴포넌트가 생성되는 것을 방지할 수 있습니다.

그런 다음 ‘C++에서 QML 객체 불러오기’에 설명된 방식과 동일하게 컴포넌트를 초기화할 수 있습니다. 단, ` createWithInitialProperties()`를 사용해야 한다는 점만 다릅니다:

component.createWithInitialProperties(QVariantMap{{u"interface"_s, QVariant::fromValue<CppInterface *>(new CppInterface)}});

인터페이스가 루트 컴포넌트에서만 사용되어야 한다는 것을 알고 있는 경우 이 방법을 사용하는 것이 좋습니다. 또한 이 방법을 사용하면 C++ 측에서 인터페이스의 시그널과 슬롯에 더 쉽게 연결할 수 있습니다.

이 두 방법 중 어느 것도 요구 사항에 맞지 않는다면, 대신 C++ 모델의 사용법을 살펴보는 것이 좋습니다.

객체 이름을 통해 로드된 QML 객체에 접근하기

QML 컴포넌트는 본질적으로 형제 노드와 자체 자식 노드를 가진 자식 노드들로 구성된 객체 트리입니다. QML 컴포넌트의 자식 객체는 ` QObject::objectName ` 속성을 사용하여 ` QObject::findChild()`를 통해 찾을 수 있습니다. 예를 들어, ` MyItem.qml `의 루트 항목에 ` Rectangle ` 항목이 자식으로 있는 경우:

import QtQuick

Item {
    width: 100; height: 100

    Rectangle {
        anchors.fill: parent
        objectName: "rect"
    }
}

이 자식 항목은 다음과 같이 찾을 수 있습니다:

QObject *rect = object->findChild<QObject*>("rect");
if (rect)
    rect->setProperty("color", "red");

한 객체가 동일한 ` objectName`을 가진 여러 자식 노드를 가질 수 있다는 점에 유의하십시오. 예를 들어, ` ListView `은 자신의 델리게이트 인스턴스를 여러 개 생성하므로, 델리게이트가 특정 `objectName`으로 선언된 경우 ` ListView `에는 동일한 ` objectName`을 가진 여러 자식 노드가 존재하게 됩니다. 이 경우 ` QObject::findChildren()`을 사용하여 일치하는 ` objectName`을 가진 모든 자식 노드를 찾을 수 있습니다.

경고: C++에서 QML 객체에 접근하여 조작하는 것은가능하지만 , 테스트 및 프로토타이핑 목적을 제외하고는 권장되는 방식이 아닙니다. QML과 C++ 통합의 장점 중 하나는 C++ 로직 및 데이터셋 백엔드와 분리된 상태로 QML에서 UI를 구현할 수 있다는 점인데, C++ 측에서 QML을 직접 조작하기 시작하면 이 장점이 무효화됩니다. 또한 이러한 접근 방식은 C++ 측의 구현에 영향을 주지 않고 QML UI를 변경하기 어렵게 만듭니다.

C++에서 QML 객체 유형의 멤버에 접근하기

속성

QML 객체에 선언된 모든 속성은 C++에서 자동으로 접근할 수 있습니다. 다음과 같은 QML 항목이 있다고 가정해 보겠습니다:

// MyItem.qml
import QtQuick

Item {
    property int someNumber: 100
}

someNumber 속성의 값은 QQmlProperty 또는 QObject::setProperty() 및 QObject::property()을 사용하여 설정하고 읽을 수 있습니다:

QQmlEngine engine;
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 엔진이 속성 변경 사항을 인식할 수 있습니다. 예를 들어, PushButton 라는 사용자 정의 유형이 있고, 이 유형에 buttonText 라는 속성이 있어 내부적으로 m_buttonText 멤버 변수의 값을 반영한다고 가정해 봅시다. 다음과 같이 멤버 변수를 직접 수정하는 것은 바람직하지 않습니다:

//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.cpp
QQmlEngine engine;
QQmlComponent component(&engine, "MyItem.qml");
QObject*object = component.create();

QString returnedValue;
QString msg = "C++에서 안녕하세요";
QMetaObject::invokeMethod(object, "myQmlFunction",
        Q_RETURN_ARG(QString, returnedValue),
        Q_ARG(QString, msg));

qDebug() << "QML function returned:" << returnedValue;
객체 삭제;

콜론 뒤에 지정된 매개변수 및 반환 유형에 유의하십시오. 값 유형과 객체 유형을 유형 이름으로 사용할 수 있습니다.

QML에서 타입을 생략하거나 ‘ var ’로 지정했다면, ` QMetaObject::invokeMethod`를 호출할 때 ` Q_RETURN_ARG()` 및 ` Q_ARG()`에 타입으로 ` QVariant `을 전달해야 합니다.

QML 시그널 연결

모든 QML 신호는 C++에서 자동으로 사용할 수 있으며, 일반적인 Qt C++ 신호와 마찬가지로 QObject::connect()을 사용하여 연결할 수 있습니다. 반대로, 신호 핸들러를 사용하면 QML 객체가 어떤 C++ 신호든 수신할 수 있습니다.

다음은 문자열 형식의 매개변수와 함께 발신되는 qmlSignal 라는 신호를 가진 QML 컴포넌트입니다. 이 신호는 QObject::connect()을 사용하여 C++ 객체의 슬롯에 연결되어 있으므로, qmlSignal 가 발신될 때마다 cppSlot() 메서드가 호출됩니다:

// MyItem.qml
import QtQuick

Item {
    id: item
    width: 100; height: 100

    signal qmlSignal(msg: string)

    MouseArea {
        anchors.fill: parent
        onClicked: item.qmlSignal("Hello from QML")
    }
}
class MyClass : public QObject
{
    Q_OBJECT
public slots:
    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++에서 해당 클래스를 가리키는 포인터로 변환됩니다:

// MyItem.qml
import QtQuick 2.0

Item {
    id: item
    width: 100; height: 100

    signal qmlSignal(anObject: Item)

    MouseArea {
        anchors.fill: parent
        onClicked: item.qmlSignal(item)
    }
}
class MyClass : public QObject
{
    Q_OBJECT
public slots:
    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();
}

© 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.