C++로 고급 QML 확장 프로그램 작성하기

BirthdayParty 기본 프로젝트

extending-qml-advanced/advanced1-Base-project

이 튜토리얼에서는 생일 파티의 예를 사용하여 QML의 몇 가지 기능을 보여줍니다. 아래에서 설명하는 다양한 기능에 대한 코드는 이 생일 파티 프로젝트를 기반으로 하며, QML 확장에 대한 첫 번째 튜토리얼의 일부 자료에 의존합니다. 그런 다음 이 간단한 예제를 확장하여 아래에서 설명하는 다양한 QML 확장 기능을 설명합니다. 각 새로운 확장 코드에 대한 전체 코드는 각 섹션의 제목 아래에 지정된 위치의 자습서에서 찾거나 이 페이지 맨 끝에 있는 코드 링크를 따라가면 찾을 수 있습니다.

기본 프로젝트는 각각 참석자와 파티 자체를 모델링하는 Person 클래스와 BirthdayParty 클래스를 정의합니다.

class Person : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL)
    Q_PROPERTY(int shoeSize READ shoeSize WRITE setShoeSize NOTIFY shoeSizeChanged FINAL)
    QML_ELEMENT
    ...
    QString m_name;
    int m_shoeSize = 0;
};

class BirthdayParty : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Person *host READ host WRITE setHost NOTIFY hostChanged FINAL)
    Q_PROPERTY(QQmlListProperty<Person> guests READ guests NOTIFY guestsChanged FINAL)
    QML_ELEMENT
    ...
    Person *m_host = nullptr;
    QList<Person *> m_guests;
};

그런 다음 파티에 대한 모든 정보를 해당 QML 파일에 저장할 수 있습니다.

BirthdayParty {
    host: Person {
        name: "Bob Jones"
        shoeSize: 12
    }
    guests: [
        Person { name: "Leo Hodges" },
        Person { name: "Jack Smith" },
        Person { name: "Anne Brown" }
    ]
}

main.cpp 파일은 누구의 생일인지, 파티에 초대된 사람이 누구인지 표시하는 간단한 셸 애플리케이션을 만듭니다.

    QQmlEngine engine;
    QQmlComponent component(&engine);
    component.loadFromModule("People", "Main");
    std::unique_ptr<BirthdayParty> party{ qobject_cast<BirthdayParty *>(component.create()) };

이 앱은 다음과 같은 파티 요약을 출력합니다.

"Bob Jones" is having a birthday!
They are inviting:
    "Leo Hodges"
    "Jack Smith"
    "Anne Brown"

다음 섹션에서는 상속 및 강제를 사용하여 Person 대신 BoyGirl 참석자에 대한 지원을 추가하는 방법, 기본 속성을 사용하여 파티 참석자를 손님으로 암시적으로 할당하는 방법, 속성을 하나씩 할당하는 대신 그룹으로 할당하는 방법, 연결된 객체를 사용하여 초대된 손님의 응답을 추적하는 방법, 속성 값 소스를 사용하여 시간에 따라 생일 축하 노래의 가사를 표시하는 방법 및 타사 객체를 QML에 노출하는 방법에 대해 설명합니다.

상속 및 강제

extending-qml-advanced/advanced2-Inheritance-and-coercion

현재 각 수행원은 사람으로 모델링되고 있습니다. 이는 너무 일반적이어서 참석자에 대해 더 많이 알 수 있으면 좋을 것 같습니다. 남학생과 여학생으로 특화하면 누가 오는지 더 잘 파악할 수 있습니다.

이를 위해 BoyGirl 클래스가 도입되었으며, 둘 다 Person 에서 상속됩니다.

class Boy : public Person
{
    Q_OBJECT
    QML_ELEMENT
public:
    using Person::Person;
};

class Girl : public Person
{
    Q_OBJECT
    QML_ELEMENT
public:
    using Person::Person;
};

Person 클래스는 변경되지 않고 BoyGirl C++ 클래스는 이 클래스의 사소한 확장입니다. 유형과 해당 QML 이름은 QML_ELEMENT 로 QML 엔진에 등록됩니다.

BirthdayPartyhostguests 속성은 여전히 Person 의 인스턴스를 사용합니다.

class BirthdayParty : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Person *host READ host WRITE setHost NOTIFY hostChanged FINAL)
    Q_PROPERTY(QQmlListProperty<Person> guests READ guests NOTIFY guestsChanged FINAL)
    QML_ELEMENT
    ...
};

Person 클래스의 구현 자체는 변경되지 않았습니다. 그러나 Person 클래스가 BoyGirl 의 공통 기반으로 용도가 변경되었으므로 Person 은 더 이상 QML에서 직접 인스턴스화할 수 없습니다. 대신 명시적으로 Boy 또는 Girl 을 인스턴스화해야 합니다.

class Person : public QObject
{
    ...
    QML_ELEMENT
    QML_UNCREATABLE("Person is an abstract base class.")
    ...
};

QML 내에서 Person 인스턴스화를 허용하지 않으려 하지만, 속성 유형으로 사용할 수 있고 다른 유형을 강제로 사용할 수 있도록 QML 엔진에 등록해야 합니다. 이것이 QML_UNCREATABLE 매크로가 하는 일입니다. Person , BoyGirl 의 세 가지 유형이 모두 QML 시스템에 등록되었으므로, 할당 시 QML은 자동으로(그리고 유형 안전하게) BoyGirl 객체를 Person 로 변환합니다.

이러한 변경 사항을 적용하면 이제 다음과 같이 참석자에 대한 추가 정보와 함께 생일 파티를 지정할 수 있습니다.

BirthdayParty {
    host: Boy {
        name: "Bob Jones"
        shoeSize: 12
    }
    guests: [
        Boy { name: "Leo Hodges" },
        Boy { name: "Jack Smith" },
        Girl { name: "Anne Brown" }
    ]
}

기본 속성

extending-qml-advanced/advanced3-Default-properties

현재 QML 파일에서 각 속성은 명시적으로 할당됩니다. 예를 들어 host 속성에는 Boy, guests 속성에는 Boy 또는 Girl 목록이 할당됩니다. 이 방법은 쉽지만 이 특정 사용 사례에서는 조금 더 간단하게 만들 수 있습니다. guests 속성을 명시적으로 할당하는 대신 파티 내부에 BoyGirl 개체를 직접 추가하고 이를 guests 에 암시적으로 할당할 수 있습니다. 호스트가 아닌 우리가 지정한 모든 참석자는 게스트가 되는 것이 합리적입니다. 이 변경은 순전히 구문상의 변경이지만 많은 상황에서 보다 자연스러운 느낌을 더할 수 있습니다.

guests 속성을 BirthdayParty 의 기본 속성으로 지정할 수 있습니다. 즉, BirthdayParty 내부에서 생성된 각 개체는 기본 속성 guests 에 암시적으로 추가됩니다. 결과 QML은 다음과 같습니다.

BirthdayParty {
    host: Boy {
        name: "Bob Jones"
        shoeSize: 12
    }

    Boy { name: "Leo Hodges" }
    Boy { name: "Jack Smith" }
    Girl { name: "Anne Brown" }
}

이 동작을 활성화하기 위해 필요한 유일한 변경 사항은 DefaultProperty 클래스 정보 어노테이션을 BirthdayParty 에 추가하여 guests 을 기본 속성으로 지정하는 것입니다.

class BirthdayParty : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Person *host READ host WRITE setHost NOTIFY hostChanged FINAL)
    Q_PROPERTY(QQmlListProperty<Person> guests READ guests NOTIFY guestsChanged FINAL)
    Q_CLASSINFO("DefaultProperty", "guests")
    QML_ELEMENT
    ...
};

이 메커니즘에 이미 익숙하실 것입니다. QML에서 Item 의 모든 하위 요소에 대한 기본 속성은 data 속성입니다. Item 의 프로퍼티에 명시적으로 추가되지 않은 모든 요소는 data 에 추가됩니다. 이렇게 하면 구조가 명확해지고 코드에서 불필요한 노이즈가 줄어듭니다.

그룹화된 속성

extending-qml-advanced/advanced4-Grouped-properties

게스트의 신발에 대한 추가 정보가 필요합니다. 사이즈 외에도 신발의 색상, 브랜드, 가격도 저장하고 싶습니다. 이 정보는 ShoeDescription 클래스에 저장됩니다.

class ShoeDescription : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int size READ size WRITE setSize NOTIFY shoeChanged FINAL)
    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY shoeChanged FINAL)
    Q_PROPERTY(QString brand READ brand WRITE setBrand NOTIFY shoeChanged FINAL)
    Q_PROPERTY(qreal price READ price WRITE setPrice NOTIFY shoeChanged FINAL)
    ...
};

이제 각 사람에게는 name 과 신발 설명 shoe 이라는 두 개의 속성이 있습니다.

class Person : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL)
    Q_PROPERTY(ShoeDescription *shoe READ shoe WRITE setShoe NOTIFY shoeChanged FINAL)
    ...
};

신발 설명의 각 요소에 대한 값을 지정하는 것은 효과가 있지만 약간 반복적입니다.

    Girl {
        name: "Anne Brown"
        shoe.size: 7
        shoe.color: "red"
        shoe.brand: "Job Macobs"
        shoe.price: 99.99
    }

그룹화된 프로퍼티는 이러한 프로퍼티를 보다 우아하게 할당하는 방법을 제공합니다. 각 속성에 값을 하나씩 할당하는 대신 개별 값을 shoe 속성에 그룹으로 전달하여 코드를 더 읽기 쉽게 만들 수 있습니다. 이 기능은 모든 QML에서 기본적으로 사용할 수 있으므로 이 기능을 활성화하기 위해 별도의 변경이 필요하지 않습니다.

    host: Boy {
        name: "Bob Jones"
        shoe { size: 12; color: "white"; brand: "Bikey"; price: 90.0 }
    }

첨부된 속성

extending-qml-advanced/advanced5-Attached-properties

호스트가 초대를 보낼 때가 되었습니다. 어떤 게스트가 언제 초대에 응답했는지 추적하려면 해당 정보를 저장할 곳이 필요합니다. BirthdayParty 객체에 저장하는 것 자체는 적합하지 않습니다. 더 좋은 방법은 파티 객체에 첨부된 객체로 응답을 저장하는 것입니다.

먼저 게스트 응답을 보관하는 BirthdayPartyAttached 클래스를 선언합니다.

class BirthdayPartyAttached : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QDate rsvp READ rsvp WRITE setRsvp NOTIFY rsvpChanged FINAL)
    QML_ANONYMOUS
    ...
};

그리고 이를 BirthdayParty 클래스에 첨부하고 qmlAttachedProperties() 을 정의하여 첨부된 객체를 반환합니다.

class BirthdayParty : public QObject
{
    ...
    QML_ATTACHED(BirthdayPartyAttached)
    ...
    static BirthdayPartyAttached *qmlAttachedProperties(QObject *);

};

이제 첨부된 객체를 QML에서 사용하여 초대된 게스트의 회신 정보를 보관할 수 있습니다.

BirthdayParty {
    Boy {
        name: "Robert Campbell"
        BirthdayParty.rsvp: Date.fromLocaleString(Qt.locale(), "2023-03-01", "yyyy-MM-dd")
    }

    Boy {
        name: "Leo Hodges"
        shoe { size: 10; color: "black"; brand: "Reebok"; price: 59.95 }
        BirthdayParty.rsvp: Date.fromLocaleString(Qt.locale(), "2023-03-03", "yyyy-MM-dd")
    }

    host: Boy {
        name: "Jack Smith"
        shoe { size: 8; color: "blue"; brand: "Puma"; price: 19.95 }
    }
}

마지막으로 다음과 같은 방법으로 정보에 액세스할 수 있습니다.

            QDate rsvpDate;
            QObject *attached = qmlAttachedPropertiesObject<BirthdayParty>(guest, false);

            if (attached)
                rsvpDate = attached->property("rsvp").toDate();

프로그램은 다음과 같이 참석할 파티에 대한 요약을 출력합니다.

"Jack Smith" is having a birthday!
He is inviting:
    "Robert Campbell" RSVP date: "Wed Mar 1 2023"
    "Leo Hodges" RSVP date: "Mon Mar 6 2023"

속성 값 소스

extending-qml-advanced/advanced6-Property-value-source

파티가 진행되는 동안 게스트는 호스트를 위해 노래를 불러야 합니다. 프로그램이 게스트에게 도움이 되도록 상황에 맞는 가사를 표시할 수 있다면 편리할 것입니다. 이를 위해 속성 값 소스를 사용하여 시간에 따라 노래의 구절을 생성합니다.

class HappyBirthdaySong : public QObject, public QQmlPropertyValueSource
{
    Q_OBJECT
    Q_INTERFACES(QQmlPropertyValueSource)
    ...
    void setTarget(const QQmlProperty &) override;

};

HappyBirthdaySong 클래스가 값 소스로 추가됩니다. QQmlPropertyValueSource 에서 상속하고 Q_INTERFACES 매크로를 사용하여 QQmlPropertyValueSource 인터페이스를 구현해야 합니다. setTarget() 함수는 이 소스가 작동하는 속성을 정의하는 데 사용됩니다. 이 경우 값 소스는 BirthdayPartyannouncement 프로퍼티에 기록하여 시간이 지남에 따라 가사를 표시합니다. 여기에는 가사의 다음 줄에 반복적으로 announcement 속성을 설정하는 내부 타이머가 있습니다.

QML에서 HappyBirthdaySongBirthdayParty 안에 인스턴스화됩니다. 서명의 on 키워드는 값 소스가 타겟팅하는 속성(이 경우 announcement)을 지정하는 데 사용됩니다. HappyBirthdaySong 객체의 name 속성도 파티 호스트의 이름에 바인딩됩니다.

BirthdayParty {
    id: party
    HappyBirthdaySong on announcement {
        name: party.host.name
    }
    ...
}

이 프로그램은 partyStarted 신호를 사용하여 파티가 시작된 시간을 표시한 다음 다음 생일 축하 문구를 반복해서 인쇄합니다.

Happy birthday to you,
Happy birthday to you,
Happy birthday dear Bob Jones,
Happy birthday to you!

외부 객체 통합

extending-qml-advanced/advanced7-Foreign-objects-integration

참석자들은 단순히 가사를 콘솔에 인쇄하는 대신 색상을 지원하는 더 멋진 디스플레이를 사용하고 싶어합니다. 프로젝트에 이를 통합하고 싶지만 현재 타사 라이브러리에서 제공되므로 QML에서 화면을 구성할 수 없습니다. 이 문제를 해결하려면 필요한 유형을 QML 엔진에 노출하여 QML에서 직접 속성을 수정할 수 있도록 해야 합니다.

디스플레이는 ThirdPartyDisplay 클래스로 제어할 수 있습니다. 이 클래스에는 표시할 텍스트의 콘텐츠와 전경색 및 배경색을 정의하는 속성이 있습니다.

class Q_DECL_EXPORT ThirdPartyDisplay : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString content READ content WRITE setContent NOTIFY contentChanged FINAL)
    Q_PROPERTY(QColor foregroundColor READ foregroundColor WRITE setForegroundColor NOTIFY colorsChanged FINAL)
    Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor NOTIFY colorsChanged FINAL)
    ...
};

이 유형을 QML에 노출하려면 QML_ELEMENT 으로 엔진에 등록하면 됩니다. 그러나 이 클래스는 수정할 수 없으므로 QML_ELEMENT 을 간단히 추가할 수 없습니다. 엔진에 유형을 등록하려면 외부에서 유형을 등록해야 합니다. 이것이 QML_FOREIGN 의 용도입니다. 다른 QML 매크로와 함께 유형에 사용되는 경우 다른 매크로는 해당 유형이 아닌 QML_FOREIGN 에 지정된 외부 유형에 적용됩니다.

class ForeignDisplay : public QObject
{
    Q_OBJECT
    QML_NAMED_ELEMENT(ThirdPartyDisplay)
    QML_FOREIGN(ThirdPartyDisplay)
};

이렇게 하면 생일파티에 디스플레이가 있는 새로운 프로퍼티가 생깁니다.

class BirthdayParty : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Person *host READ host WRITE setHost NOTIFY hostChanged FINAL)
    Q_PROPERTY(QQmlListProperty<Person> guests READ guests NOTIFY guestsChanged FINAL)
    Q_PROPERTY(QString announcement READ announcement WRITE setAnnouncement NOTIFY announcementChanged FINAL)
    Q_PROPERTY(ThirdPartyDisplay *display READ display WRITE setDisplay NOTIFY displayChanged FINAL)
    ...
};

그리고 QML에서는 멋진 세 번째 디스플레이의 텍스트 색상을 명시적으로 설정할 수 있습니다.

BirthdayParty {
    display: ThirdPartyDisplay {
        foregroundColor: "black"
        backgroundColor: "white"
    }
    ...
}

생일파티의 announcement 속성을 설정하면 이제 메시지가 자체적으로 인쇄되는 대신 멋진 디스플레이로 전송됩니다.

void BirthdayParty::setAnnouncement(const QString &announcement)
{
    if (m_announcement != announcement) {
        m_announcement = announcement;
        emit announcementChanged();
    }
    m_display->setContent(announcement);
}

그러면 출력은 이전 섹션과 비슷하게 반복해서 다음과 같이 표시됩니다.

[Fancy ThirdPartyDisplay] Happy birthday to you,
[Fancy ThirdPartyDisplay] Happy birthday to you,
[Fancy ThirdPartyDisplay] Happy birthday dear Bob Jones,
[Fancy ThirdPartyDisplay] Happy birthday to you!

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.