Singletons en QML
En QML, un singleton es un objeto que se crea como máximo una vez por engine. En esta guía, explicaremos cómo crear singletons y cómo utilizarlos. También proporcionaremos algunas prácticas recomendadas para trabajar con singletons.
¿Cómo se pueden crear singletons en QML?
Existen dos formas distintas de crear singletons en QML. Puede definir el singleton en un archivo QML o registrarlo desde C++.
Definición de singletons en QML
Para definir un singleton en QML, primero tienes que añadir
pragma Singleton
al principio del archivo. Hay un paso más: Tendrás que añadir una entrada al archivo qmldir del módulo QML.
Usando qt_add_qml_module (CMake)
Cuando se utiliza CMake, el qmldir es creado automáticamente por qt_add_qml_module. Para indicar que el archivo QML debe convertirse en un singleton, es necesario establecer la propiedad QT_QML_SINGLETON_TYPE file en él:
set_source_files_properties(MySingleton.qml
PROPERTIES QT_QML_SINGLETON_TYPE TRUE)Puede pasar varios archivos a la vez a set_source_files_properties:
set(plain_qml_files
MyItem1.qml
MyItem2.qml
FancyButton.qml
)
set(qml_singletons
MySingleton.qml
MyOtherSingleton.qml
)
set_source_files_properties(${qml_singletons}
PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
qt_add_qml_module(myapp
URI MyModule
QML_FILES ${plain_qml_files} ${qml_singletons}
)Nota: set_source_files_properties tiene que ser llamado antes de la creación del módulo. qt_add_qml_module
Sin qt_add_qml_module
Si no está utilizando qt_add_qml_module, tendrá que crear manualmente un archivo qmldir. Allí, tendrá que marcar sus singletons en consecuencia:
module MyModule singleton MySingleton 1.0 MySingleton.qml singleton MyOtherSingleton 1.0 MyOtherSingleton.qml
Véase también Declaración de tipos de objetos para más detalles.
Definición de singletons en C
Hay varias formas de exponer singletons a QML desde C++. La principal diferencia depende de si debe crearse una nueva instancia de una clase cuando el motor QML la necesite, o si debe exponerse algún objeto existente a un programa QML.
Registro de una clase para proporcionar singletons
La forma más sencilla de definir un singleton es tener una clase construible por defecto, que derive de QObject y marcarla con las macros QML_SINGLETON y QML_ELEMENT.
class MySingleton : public QObject { Q_OBJECT QML_SINGLETON QML_ELEMENT public: MySingleton(QObject *parent = nullptr) : QObject(parent) { // ... } };
Esto registrará la clase MySingleton con el nombre MySingleton en el módulo QML al que pertenezca el archivo. Si desea exponerla con un nombre diferente, puede utilizar QML_NAMED_ELEMENT en su lugar.
Si la clase no puede construirse por defecto, o si necesita acceder a QQmlEngine en la que se instanciará el singleton, es posible utilizar una función de creación estática en su lugar. Debe tener la firma MySingleton *create(QQmlEngine *, QJSEngine *), donde MySingleton es el tipo de la clase que se registra.
class MyNonDefaultConstructibleSingleton : public QObject { Q_OBJECT QML_SINGLETON QML_NAMED_ELEMENT(MySingleton) public: MyNonDefaultConstructibleSingleton(QJSValue id, QObject *parent = nullptr) : QObject(parent) , m_symbol(std::move(id)) {} static MyNonDefaultConstructibleSingleton *create(QQmlEngine *qmlEngine, QJSEngine *) { return new MyNonDefaultConstructibleSingleton(qmlEngine->newSymbol(u"MySingleton"_s)); } private: QJSValue m_symbol; };
Nota: La función create recibe un parámetro QJSEngine y otro QQmlEngine. Esto es por razones históricas. Ambos apuntan al mismo objeto, que de hecho es QQmlEngine.
Exponer un objeto existente como singleton
A veces, tienes un objeto existente que puede haber sido creado a través de alguna API de terceros. A menudo, la elección correcta en este caso es tener un singleton, que expone esos objetos como sus propiedades (ver Agrupar datos relacionados). Pero si ese no es el caso, por ejemplo porque sólo hay un único objeto que necesita ser expuesto, utilice el siguiente enfoque para exponer una instancia del tipo MySingleton al motor. Primero exponemos el Singleton como foreign type:
struct SingletonForeign { Q_GADGET QML_FOREIGN(MySingleton) QML_SINGLETON QML_NAMED_ELEMENT(MySingleton) public: inline static MySingleton *s_singletonInstance = nullptr; static MySingleton *create(QQmlEngine *, QJSEngine *engine) { // The instance has to exist before it is used. We cannot replace it. Q_ASSERT(s_singletonInstance); // The engine has to have the same thread affinity as the singleton. Q_ASSERT(engine->thread() == s_singletonInstance->thread()); // There can only be one engine accessing the singleton. if (s_engine) Q_ASSERT(engine == s_engine); else s_engine = engine; // Explicitly specify C++ ownership so that the engine doesn't delete // the instance. QJSEngine::setObjectOwnership(s_singletonInstance, QJSEngine::CppOwnership); return s_singletonInstance; } private: inline static QJSEngine *s_engine = nullptr; };
Luego exponemos SingletonForeign::s_singletonInstance antes de iniciar el primer motor
SingletonForeign::s_singletonInstance = getSingletonInstance(); QQmlApplicationEngine engine; engine.loadFromModule("MyModule", "Main");
Nota: Puede ser muy tentador utilizar simplemente qmlRegisterSingletonInstance en este caso. Sin embargo, ten cuidado con las trampas del registro de tipos imperativo que se enumeran en la siguiente sección.
Registro imperativo de tipos
Antes de Qt 5.15, todos los tipos, incluyendo los singletons se registraban a través de la API qmlRegisterType. Los singletons específicamente se registraban a través de qmlRegisterSingletonType o qmlRegisterSingletonInstance. Además de la pequeña molestia de tener que repetir el nombre del módulo para cada tipo y el desacoplamiento forzado de la declaración de la clase y su registro, el mayor problema con ese enfoque era que no era amigable con las herramientas: no era estáticamente posible extraer toda la información necesaria sobre los tipos de un módulo en tiempo de compilación. El registro declarativo resolvió este problema.
Nota: Queda un caso de uso para la API imperativa qmlRegisterType: Es una forma de exponer un singleton de tipo noQObject como una propiedad var a través de the QJSValue based qmlRegisterSingletonType overload . Prefiera la alternativa: Exponer ese valor como la propiedad de un singleton basado en (QObject), para que la información de tipo esté disponible.
Acceso a singletons
Se puede acceder a los singletons tanto desde QML como desde C++. En QML, es necesario importar el módulo que los contiene. Después, puedes acceder al singleton a través de su nombre. La lectura de sus propiedades y la escritura en ellas dentro de contextos JavaScript se realiza del mismo modo que con los objetos normales:
import QtQuick import MyModule Item { x: MySingleton.posX Component.onCompleted: MySingleton.ready = true; }
No es posible establecer vínculos en las propiedades de un singleton; sin embargo, si es necesario, se puede utilizar un elemento Binding para conseguir el mismo resultado:
import QtQuick import MyModule Item { id: root Binding { target: MySingleton property: "posX" value: root.x } }
Nota: Hay que tener cuidado al instalar un binding en una propiedad singleton: Si se hace por más de un archivo, los resultados no están definidos.
Pautas para (no) utilizar singletons
Los singletons te permiten exponer al motor datos a los que es necesario acceder en múltiples lugares. Pueden ser ajustes compartidos globalmente, como el espaciado entre elementos, o modelos de datos que necesitan ser mostrados en múltiples lugares. En comparación con las propiedades de contexto, que pueden resolver un caso de uso similar, tienen la ventaja de ser tipadas, de estar soportadas por herramientas como la herramienta QML Language Servery, además, suelen ser más rápidas en tiempo de ejecución.
Se recomienda no registrar demasiados singletons en un módulo: Los singletons, una vez creados, permanecen vivos hasta que el propio motor se destruye y tienen los inconvenientes del estado compartido, ya que forman parte del estado global. Por lo tanto, considera el uso de las siguientes técnicas para reducir la cantidad de singletons en tu aplicación:
Agrupar datos relacionados
Añadir un singleton por cada objeto que quieras exponer añade bastante trabajo. La mayoría de las veces, tiene más sentido agrupar los datos que quieres exponer como propiedades de un único singleton. Supongamos, por ejemplo, que quieres crear un lector de libros electrónicos y necesitas exponer tres abstract item models, uno para los libros locales y dos para las fuentes remotas. En lugar de repetir el proceso para exponer los objetos existentes tres veces, puedes crear un singleton y configurarlo antes de iniciar la aplicación principal:
class GlobalState : QObject { Q_OBJECT QML_ELEMENT QML_SINGLETON Q_PROPERTY(QAbstractItemModel* localBooks MEMBER localBooks) Q_PROPERTY(QAbstractItemModel* digitalStoreFront MEMBER digitalStoreFront) Q_PROPERTY(QAbstractItemModel* publicLibrary MEMBER publicLibrary) public: QAbstractItemModel* localBooks; QAbstractItemModel* digitalStoreFront; QAbstractItemModel* publicLibrary }; int main() { QQmlApplicationEngine engine; auto globalState = engine.singletonInstance<GlobalState *>("MyModule", "GlobalState"); globalState->localBooks = getLocalBooks(); globalState->digitalStoreFront = setupLoalStoreFront(); globalState->publicLibrary = accessPublicLibrary(); engine.loadFromModule("MyModule", "Main"); }
Utilizar instancias de objetos
En la última sección, tuvimos el ejemplo de exponer tres modelos como miembros de un singleton. Esto puede ser útil cuando los modelos necesitan ser utilizados en múltiples lugares, o cuando son proporcionados por alguna API externa sobre la que no tenemos control. Sin embargo, si sólo necesitamos los modelos en un único lugar, puede tener más sentido tenerlos como un tipo instanciable. Volviendo al ejemplo anterior, podemos añadir una clase instanciable RemoteBookModel, y luego instanciarla dentro del archivo QML del navegador de libros:
// remotebookmodel.h class RemoteBookModel : public QAbstractItemModel { Q_OBJECT QML_ELEMENT Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) // ... };
// bookbrowser.qml Row { ListView { model: RemoteBookModel { url: "www.public-lib.example"} } ListView { model: RemoteBookModel { url: "www.store-front.example"} } }
Pasar el estado inicial
Aunque los singletons se pueden utilizar para pasar estado a QML, son un desperdicio cuando el estado sólo se necesita para la configuración inicial de la aplicación. En ese caso, a menudo es posible utilizar QQmlApplicationEngine::setInitialProperties. Por ejemplo, es posible que desee establecer Window::visibility en pantalla completa si se ha establecido una bandera de línea de comandos correspondiente:
QQmlApplicationEngine engine; if (parser.isSet(fullScreenOption)) { // assumes root item is ApplicationWindow engine.setInitialProperties( { "visibility", QVariant::fromValue(QWindow::FullScreen)} ); } engine.loadFromModule("MyModule, "Main");
© 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.