Details
-
Suggestion
-
Resolution: Done
-
Not Evaluated
-
5.10
-
None
-
fee8944cbe2a976e1b4a71c5df048e84a0bc6db0 1b996c138f6afae7ccf9d9ec87036768fd8ef329
Description
Currently it is not possible to use Q_GADGET in template classes (because of moc limitations etc.).
However it is perfectly possible to define a Q_GADGET enabled base class and inherit a template class (subsequently called GT) adding type bound (type safe) functionality. This is similar to the approach of using template functionality in Q_OBJECT based class hierarchies.
The constraint with both is that all template class instances will share the metaobject of the base class, so you obviously cannot add new slots, invokables or properties and the metaobject classname is that of the base class - not a big deal in practice.
The problem is that on the QML side the V4 engine won't wrap a QVariant of an GT instance correctly as a Q_GADGET (see ExecutionEngine::metaTypeToJS) because on registration the QMetaType::IsGadget flag is not set for the template classes. So a QVariant wrapping a GT instance cannot be used on the QML side (no property access etc).
Reason for this is the trait magic applied in the background. Without Q_GADGET in the template class (which is obviously not allowed) it is not tagged with "void qt_check_for_QGADGET_macro()" and thus the flag is not set during registration.
Putting this tag manually into the template class does the trick. Now the GT QVariant can be used nicely on the Qml side.
Proposal: Add a Q_GADGET_INHERITED_TEMPLATE macro that only adds the tag to a template class.
Please check the following complete example - if the Q_GADGET_INHERITED_TEMPLATE macro is being removed from the template class, property access and toString invocation on the Qml side will fail for the typed descriptor instances:
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include <QDebug> // generic descriptor to address some object // this is the Q_GADGET enabled base class class Descriptor { Q_GADGET Q_PROPERTY(QString accountId MEMBER m_accountId) Q_PROPERTY(QString objectId MEMBER m_objectId) public: Descriptor(const char *tn, const QString &accountId, const QString &objectId) : m_tn(tn), m_accountId(accountId), m_objectId(objectId) {} Descriptor() = default; const char *tn() const { return m_tn; } QString accountId() const { return m_accountId; } QString objectId() const { return m_objectId; } Q_INVOKABLE QString toString() const { return "Descriptor (" + QString::fromLatin1(m_tn) + ", " + m_accountId + ", " + m_objectId + ")"; } private: const char *m_tn = 0; QString m_accountId; QString m_objectId; }; // fake the Q_GAGDET type level stuff so that // (QMetaType().flags() & QMetaType::IsGadget) == true // This will make QVariants of TypedDescriptor<T> work in Qml! // Note that the template instances share the meta object with Descriptor // so the classname info will be "Descriptor". // This depends on Qt compile time internals! #define Q_GADGET_INHERITED_TEMPLATE \ public: \ void qt_check_for_QGADGET_macro(); \ private: \ // this is the type safe descriptor template <typename T> class TypedDescriptor : public Descriptor { Q_GADGET_INHERITED_TEMPLATE public: TypedDescriptor(const QString &accountId, const QString &objectId) : Descriptor(typeid(T).name(), accountId, objectId) {} TypedDescriptor(const T &o) : Descriptor(o.getDescriptor()) {} TypedDescriptor(const TypedDescriptor &other) = default; TypedDescriptor() = default; }; // descriptors bound to some c++ type so we have type safety on c++ side class TypeA; typedef TypedDescriptor<TypeA> TypeADescriptor; class TypeB; typedef TypedDescriptor<TypeB> TypeBDescriptor; // dummy types class TypeA { }; class TypeB { }; Q_DECLARE_METATYPE(Descriptor) Q_DECLARE_METATYPE(TypeADescriptor) Q_DECLARE_METATYPE(TypeBDescriptor) int main(int argc, char *argv[]) { qRegisterMetaType<Descriptor>(); qRegisterMetaType<TypeADescriptor>(); qRegisterMetaType<TypeBDescriptor>(); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; QQmlContext *c = engine.rootContext(); c->setContextProperty("descriptor", QVariant::fromValue(Descriptor("generic", "account1", "someID"))); c->setContextProperty("descriptor_a", QVariant::fromValue(TypeADescriptor("account1", "id_for_type_a"))); c->setContextProperty("descriptor_b", QVariant::fromValue(TypeBDescriptor("account1", "id_for_type_b"))); engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); if (engine.rootObjects().isEmpty()) return -1; return app.exec(); } #include "main.moc"
import QtQuick 2.10 import QtQuick.Window 2.10 Window { visible: true width: 800 height: 480 title: qsTr("TypedDescriptor Q_GADGET Demo") Text { text: "Descriptors in Qml land:<br><br>" + "generic: " + descriptor + " - " + descriptor.accountId + " toString " + descriptor.toString() + "<br>" + "type a " + descriptor_a + " - " + descriptor_a.accountId + " toString " + descriptor_a.toString() + "<br>" + "type b " + descriptor_b + " - " + descriptor_b.accountId + " toString " + descriptor_b.toString() } }