Escribir extensiones QML avanzadas con C++
Proyecto base BirthdayParty
extending-qml-advanced/advanced1-Base-project
Este tutorial utiliza el ejemplo de una fiesta de cumpleaños para demostrar algunas de las características de QML. El código para las diversas características que se explican a continuación se basa en este proyecto de fiesta de cumpleaños y se apoya en parte del material del primer tutorial sobre extensiones QML. Este sencillo ejemplo se amplía para ilustrar las distintas extensiones QML que se explican a continuación. El código completo de cada nueva extensión del código se puede encontrar en los tutoriales en la ubicación especificada bajo el título de cada sección o siguiendo el enlace al código al final de esta página.
El proyecto base define la clase Person y la clase BirthdayParty, que modelan los asistentes y la fiesta en sí respectivamente.
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; };
Toda la información sobre la fiesta puede almacenarse en el archivo QML correspondiente.
BirthdayParty { host: Person { name: "Bob Jones" shoeSize: 12 } guests: [ Person { name: "Leo Hodges" }, Person { name: "Jack Smith" }, Person { name: "Anne Brown" } ] }
El archivo main.cpp crea una sencilla aplicación shell que muestra de quién es el cumpleaños y quién está invitado a su fiesta.
QQmlEngine engine; QQmlComponent component(&engine); component.loadFromModule("People", "Main"); std::unique_ptr<BirthdayParty> party{ qobject_cast<BirthdayParty *>(component.create()) };
La aplicación muestra el siguiente resumen de la fiesta.
"Bob Jones" is having a birthday!
They are inviting:
"Leo Hodges"
"Jack Smith"
"Anne Brown"En las secciones siguientes se explica cómo añadir soporte para asistentes a Boy y Girl en lugar de sólo Person utilizando herencia y coerción, cómo utilizar propiedades predeterminadas para asignar implícitamente asistentes a la fiesta como invitados, cómo asignar propiedades como grupos en lugar de una a una, cómo utilizar objetos adjuntos para realizar un seguimiento de las respuestas de los invitados, cómo utilizar una fuente de valores de propiedad para mostrar la letra de la canción de cumpleaños feliz a lo largo del tiempo y cómo exponer objetos de terceros a QML.
Herencia y coerción
extending-qml-advanced/advanced2-Inheritance-and-coercion
En este momento, cada asistente está siendo modelado como una persona. Esto es un poco demasiado genérico y estaría bien poder saber más sobre los asistentes. Al especializarlos como chicos y chicas, ya podemos hacernos una mejor idea de quién va a venir.
Para ello, se introducen las clases Boy y Girl, ambas heredan de 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; };
La clase Person permanece inalterada y las clases C++ Boy y Girl son extensiones triviales de la misma. Los tipos y su nombre QML se registran en el motor QML con QML_ELEMENT.
Observe que las propiedades host y guests de BirthdayParty siguen tomando instancias de 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 ... };
La implementación de la clase Person en sí no se ha modificado. Sin embargo, dado que la clase Person se ha reutilizado como base común para Boy y Girl, Person ya no debería poder instanciarse directamente desde QML. En su lugar, debería instanciarse explícitamente Boy o Girl.
class Person : public QObject { ... QML_ELEMENT QML_UNCREATABLE("Person is an abstract base class.") ... };
Aunque no queremos permitir la instanciación de Person desde QML, sigue siendo necesario registrarlo en el motor QML para que pueda utilizarse como tipo de propiedad y se le puedan aplicar otros tipos. Esto es lo que hace la macro QML_UNCREATABLE. Como los tres tipos, Person, Boy y Girl, se han registrado en el sistema QML, al asignarlos, QML convierte automáticamente (y con seguridad de tipo) los objetos Boy y Girl en Person.
Una vez realizados estos cambios, ya podemos especificar la fiesta de cumpleaños con la información adicional sobre los asistentes que se indica a continuación.
BirthdayParty { host: Boy { name: "Bob Jones" shoeSize: 12 } guests: [ Boy { name: "Leo Hodges" }, Boy { name: "Jack Smith" }, Girl { name: "Anne Brown" } ] }
Propiedades por defecto
extending-qml-advanced/advanced3-Default-properties
Actualmente, en el archivo QML, cada propiedad se asigna explícitamente. Por ejemplo, a la propiedad host se le asigna un Boy y a la propiedad guests se le asigna una lista de Boy o Girl. Esto es fácil pero se puede simplificar un poco para este caso de uso específico. En lugar de asignar la propiedad guests explícitamente, podemos añadir los objetos Boy y Girl dentro de la fiesta directamente y hacer que se asignen a guests implícitamente. Tiene sentido que todos los asistentes que especifiquemos, y que no sean el anfitrión, sean invitados. Este cambio es puramente sintáctico pero puede dar una sensación más natural en muchas situaciones.
La propiedad guests se puede designar como la propiedad por defecto de BirthdayParty. Lo que significa que cada objeto creado dentro de un BirthdayParty se anexa implícitamente a la propiedad por defecto guests. El QML resultante tiene este aspecto.
BirthdayParty { host: Boy { name: "Bob Jones" shoeSize: 12 } Boy { name: "Leo Hodges" } Boy { name: "Jack Smith" } Girl { name: "Anne Brown" } }
El único cambio necesario para activar este comportamiento es añadir la anotación DefaultProperty class info a BirthdayParty para designar guests como su propiedad por defecto.
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 ... };
Puede que ya esté familiarizado con este mecanismo. La propiedad por defecto para todos los descendientes de Item en QML es la propiedad data. Todos los elementos que no se añadan explícitamente a una propiedad de Item se añadirán a data. Esto aclara la estructura y reduce el ruido innecesario en el código.
Propiedades agrupadas
extending-qml-advanced/advanced4-Grouped-properties
Se necesita más información sobre los zapatos de los invitados. Aparte de su talla, también queremos almacenar el color, la marca y el precio de los zapatos. Esta información se almacena en una clase 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) ... };
Cada persona tiene ahora dos propiedades, una name y una descripción del zapato 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) ... };
Especificar los valores de cada elemento de la descripción del zapato funciona, pero es un poco repetitivo.
Girl { name: "Anne Brown" shoe.size: 7 shoe.color: "red" shoe.brand: "Job Macobs" shoe.price: 99.99 }
Las propiedades agrupadas proporcionan una forma más elegante de asignar estas propiedades. En lugar de asignar los valores a cada propiedad uno por uno, los valores individuales se pueden pasar como un grupo a la propiedad shoe haciendo el código más legible. No es necesario realizar ningún cambio para activar esta función, ya que está disponible por defecto para todo QML.
host: Boy { name: "Bob Jones" shoe { size: 12; color: "white"; brand: "Bikey"; price: 90.0 } }
Propiedades adjuntas
extending-qml-advanced/advanced5-Attached-properties
Ha llegado el momento de que el anfitrión envíe las invitaciones. Para llevar la cuenta de qué invitados han respondido a la invitación y cuándo, necesitamos algún lugar donde almacenar esa información. Almacenarla en el propio objeto BirthdayParty no encajaría realmente. Una forma mejor sería almacenar las respuestas como objetos adjuntos al objeto fiesta.
Primero, declaramos la clase BirthdayPartyAttached que contiene las respuestas de los invitados.
class BirthdayPartyAttached : public QObject { Q_OBJECT Q_PROPERTY(QDate rsvp READ rsvp WRITE setRsvp NOTIFY rsvpChanged FINAL) QML_ANONYMOUS ... };
La adjuntamos a la clase BirthdayParty y definimos qmlAttachedProperties() para que devuelva el objeto adjunto.
class BirthdayParty : public QObject { ... QML_ATTACHED(BirthdayPartyAttached) ... static BirthdayPartyAttached *qmlAttachedProperties(QObject *); };
Ahora, los objetos adjuntos se pueden utilizar en el QML para contener la información rsvp de los invitados.
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 } } }
Finalmente, se puede acceder a la información de la siguiente manera.
QDate rsvpDate; QObject *attached = qmlAttachedPropertiesObject<BirthdayParty>(guest, false); if (attached) rsvpDate = attached->property("rsvp").toDate();
El programa devuelve el siguiente resumen de la fiesta que se va a celebrar.
"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"Propiedad Valor Fuente
extending-qml-advanced/advanced6-Property-value-source
Durante la fiesta los invitados tienen que cantar para el anfitrión. Sería práctico que el programa pudiera mostrar la letra de la canción adaptada a la ocasión para ayudar a los invitados. Para ello, se utiliza una propiedad value source para generar las estrofas de la canción a lo largo del tiempo.
class HappyBirthdaySong : public QObject, public QQmlPropertyValueSource { Q_OBJECT Q_INTERFACES(QQmlPropertyValueSource) ... void setTarget(const QQmlProperty &) override; };
Como fuente de valores se añade la clase HappyBirthdaySong. Debe heredar de QQmlPropertyValueSource e implementar la interfaz QQmlPropertyValueSource con la macro Q_INTERFACES. La función setTarget() se utiliza para definir sobre qué propiedad actúa esta fuente. En este caso, la fuente de valor escribe en la propiedad announcement de BirthdayParty para mostrar la letra de la canción a lo largo del tiempo. Tiene un temporizador interno que hace que la propiedad announcement de la parte se establezca en la siguiente línea de la letra repetidamente.
En QML, un HappyBirthdaySong se instancia dentro del BirthdayParty. La palabra clave on de su firma se utiliza para especificar la propiedad a la que se dirige la fuente de valor, en este caso announcement. La propiedad name del objeto HappyBirthdaySong también está vinculada al nombre del anfitrión de la fiesta.
BirthdayParty { id: party HappyBirthdaySong on announcement { name: party.host.name } ... }
El programa muestra la hora a la que empezó la fiesta utilizando la señal partyStarted y luego imprime los siguientes versos de feliz cumpleaños una y otra vez.
Happy birthday to you, Happy birthday to you, Happy birthday dear Bob Jones, Happy birthday to you!
Integración de objetos extraños
extending-qml-advanced/advanced7-Foreign-objects-integration
En lugar de limitarse a imprimir las letras en la consola, a los asistentes les gustaría utilizar una pantalla más elegante con soporte para colores. Les gustaría integrarla en el proyecto pero actualmente no es posible configurar la pantalla desde QML porque proviene de una librería de terceros. Para solucionar esto, es necesario exponer los tipos necesarios al motor QML para que sus propiedades estén disponibles para ser modificadas en QML directamente.
La pantalla puede ser controlada por la clase ThirdPartyDisplay. Tiene propiedades para definir el contenido y los colores de primer y segundo plano del texto a mostrar.
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) ... };
Para exponer este tipo a QML, podemos registrarlo en el motor con QML_ELEMENT. Sin embargo, dado que la clase no es accesible para su modificación, no se le puede añadir simplemente QML_ELEMENT. Para registrar el tipo con el motor, el tipo necesita ser registrado desde el exterior. Para esto está QML_FOREIGN. Cuando se utilizan en un tipo junto con otras macros QML, las otras macros no se aplican al tipo en el que residen, sino al tipo externo designado por QML_FOREIGN.
class ForeignDisplay : public QObject { Q_OBJECT QML_NAMED_ELEMENT(ThirdPartyDisplay) QML_FOREIGN(ThirdPartyDisplay) };
De este modo, el BirthdayParty tiene ahora una nueva propiedad con el display.
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) ... };
Y, en QML, los colores del texto en la tercera pantalla de fantasía se pueden establecer explícitamente.
BirthdayParty { display: ThirdPartyDisplay { foregroundColor: "black" backgroundColor: "white" } ... }
Al establecer la propiedad announcement de BirthdayParty se envía el mensaje a la pantalla de fantasía en lugar de imprimirlo él mismo.
void BirthdayParty::setAnnouncement(const QString &announcement) { if (m_announcement != announcement) { m_announcement = announcement; emit announcementChanged(); } m_display->setContent(announcement); }
La salida entonces se ve así una y otra vez similar a la sección anterior.
[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!
Véase también Especificar propiedades por defecto y propiedades padre para tipos de objeto QML, Propiedades agrupadas, Proporcionar propiedades adjuntas, Fuentes de valores de propiedades y Registrar tipos ajenos.
© 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.