QML: QMetaProperty::read unable to handle for some enums defined in C++, but not all
-
Hi all,
I´m having some weird problems with Q_ENUM()s and QML, where for two enums declared in an almost identical way, QML picks up on one of them (
GeneralCommandType
), while the other (ModelStatus
) is not detected and QObject using the enum as a Q_PROPERTY will give the dreaded "QMetaProperty::read: unable to handle unregistered datatype ´ModelStatus´ for property ´Jellyfin::ViewModel::ModelStatusTest::status´".The
GeneralCommandType
enumeration is defined as follows in its header file:#include <QObject> namespace Jellyfin { namespace DTO { class GeneralCommandTypeClass { Q_GADGET public: enum Value { EnumNotSet, MoveUp, … Play, }; Q_ENUM(Value) private: explicit GeneralCommandTypeClass(); }; using GeneralCommandType = GeneralCommandTypeClass::Value; } // NS DTO } // NS Jellyfin // Snippet from https://github.com/HenkKalkwater/harbour-sailfin/blob/89fef6d7f49f7fc45456826c1499eea4154ce559/core/include/JellyfinQt/dto/generalcommandtype.h
And the
ModelStatus
enumeration, is defined as follows in its header file:#include <QObject> namespace Jellyfin { namespace ViewModel { class ModelStatusClass { Q_GADGET public: enum Value { Uninitialised, Loading, Ready, Error, LoadingMore }; Q_ENUM(Value) private: explicit ModelStatusClass(); }; using ModelStatus = ModelStatusClass::Value; // Snippet from https://github.com/HenkKalkwater/harbour-sailfin/blob/89fef6d7f49f7fc45456826c1499eea4154ce559/core/include/JellyfinQt/viewmodel/modelstatus.h
Both are registered in the same function as follows:
namespace Jellyfin { void registerTypes(const char *uri) { qmlRegisterType<ViewModel::ModelStatusTest>(uri, 1, 0, "ModelStatusTest"); qmlRegisterUncreatableType<DTO::GeneralCommandTypeClass>(uri, 1, 0, "GeneralCommandType", "Is an enum"); qmlRegisterUncreatableType<ViewModel::ModelStatusClass>(uri, 1, 0, "ModelStatus", "Is an enum"); } } // Snippet from https://github.com/HenkKalkwater/harbour-sailfin/blob/89fef6d7f49f7fc45456826c1499eea4154ce559/core/src/jellyfin.cpp
The
GeneralCommandType
enumaration works fine within QML:pragma Singleton import org.example.Jellyfin as J J.ApiClient { supportedCommands: [J.GeneralCommandType.Play] } // Snippet from https://github.com/HenkKalkwater/harbour-sailfin/blob/89fef6d7f49f7fc45456826c1499eea4154ce559/qtquick/qml/ApiClient.qml
where supportedCommands is a
Q_PROPERTY(QList<DTO::GeneraldCommandType> supportedCommands …)
. This generates no warnings at all and the C++ code is able to see the enum values just fine.ModelStatus
is used as follows, but does not work:import QtQuick 2.12 import QtQuick.Controls 2.12 import org.example.Jellyfin 1.0 as J Page { … Text { id: simpleLog text: "Simple log: \n" } J.ModelStatusTest { status: J.ModelStatus.Uninitialized // Line 38 of MainPage.qml onStatusChanged: { simpleLog.text += new Date().toString() + ": " + status + "\n" } } … } // Snippet from https://github.com/HenkKalkwater/harbour-sailfin/blob/89fef6d7f49f7fc45456826c1499eea4154ce559/qtquick/qml/pages/MainPage.qml
This gives the following warnings:
qrc:/qml/pages/MainPage.qml:38:9: Unable to assign [undefined] to [unknown property type] QMetaProperty::read: Unable to handle unregistered datatype 'ModelStatus' for property 'Jellyfin::ViewModel::ModelStatusTest::status'
ModelStatusTest
is a simple test class in C++ that changes the value of itsstatus
property once in a while:namespace Jellyfin { namespace ViewModel { class ModelStatusTest : public QObject { Q_OBJECT public: explicit ModelStatusTest(QObject *parent = nullptr) : QObject(parent) { m_timer.setInterval(500); connect(&m_timer, &QTimer::timeout, this, &ModelStatusTest::rotateStatus); m_timer.setSingleShot(false); m_timer.start(); } Q_PROPERTY(ModelStatus status READ status WRITE setStatus NOTIFY statusChanged) ModelStatus status() const { return m_status; } void setStatus(ModelStatus newStatus) { m_status = newStatus; emit statusChanged(); } signals: void statusChanged(); private slots: void rotateStatus() { setStatus(static_cast<ModelStatus>((m_status + 1) % ModelStatus::LoadingMore)); } private: ModelStatus m_status = ModelStatus::Uninitialised; QTimer m_timer; }; } // NS ViewModel } // NS Jellyfin // Snippet from https://github.com/HenkKalkwater/harbour-sailfin/blob/89fef6d7f49f7fc45456826c1499eea4154ce559/core/include/JellyfinQt/viewmodel/modelstatus.h
I´ve tried several different things:
- I´ve tried renaming
ModelStatus
toJModelStatus
, as maybe the nameModelStatus
clashed with something already within the Qt library. - I´ve tried renaming
ModelStatus::Value
toModelStatus::ModelStatusValue
as maybe havingQ_ENUM(Value)
in two differendQ_GADGET
s causes conflicts. - I´ve tried moving both enumerations into the same namespace, which does not work either.
- I´ve tried explicitly callling
qRegisterMetaType<Jellyfin::ModelStatus::Value>
- Clean rebuilding the entire project
- Double checking that al header and implementation files are passed to the
add_library(JellyfinQt …)
call.
While poking around with GammaRay in the MetaTypes tab, it shows
Jellyfin::DTO::GeneralCommandTypeClass*
with the flagsMovableType, WasDeclaredAsMetaType
andJellyfin::DTO::GeneralCommandTypeClass::Value
with the flagsMovableType, IsEnumeration, WasDeclaredAsMetaType
, but for the ModelStatus it only showsJellyfin::ViewModel::ModelStatus*
with the flagsMovableType, WasDeclaredAsMetaType
. It does not show theJellyfin::ViewModel::ModelStatus::Value
, which I would expect.When going to the Meta Object tab, it interestingly does show
Jellyfin::ViewModel::ModelStatus
, with the "Enums" tab showing theModelStatus
enum with all of its values. What is going on?I´m using Qt 5.12.2 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC, 10.2.0), if that makes any difference.
Edit: added snippet source links
- I´ve tried renaming
-
@HenkKalkwater said in QML: QMetaProperty::read unable to handle for some enums defined in C++, but not all:
That´s true. After the definition of ModelStatusClass, there is a line with using ModelStatus = ModelStatusClass::Value;, which also should be included within my snippet.
Maybe, but Qt uses introspection to find slot.
This introspection is based on string comparisons, which uses full namespaces.
This is why using namespaces with QML or SIGNALS/SLOTS is possible, but you have to be very clean!In fact writing
using ModelStatus = ModelStatusClass::Value;
makes the C++ compiler happy, but the QMeta is not aware about this!
If you want to use namespace in combination with QML/SIGNALS/SLOTS, then always use complete namespaces for all signals/slots/members signatures in header to ensure introspection will work.I am pretty sure this will solve your issue:
namespace Jellyfin { namespace ViewModel { class ModelStatusTest : public QObject { Q_OBJECT public: explicit ModelStatusTest(QObject *parent = nullptr) : QObject(parent) { m_timer.setInterval(500); connect(&m_timer, &QTimer::timeout, this, &ModelStatusTest::rotateStatus); m_timer.setSingleShot(false); m_timer.start(); } Q_PROPERTY(Jellyfin::ViewModel::ModelStatusClass::Value status READ status WRITE setStatus NOTIFY statusChanged) Jellyfin::ViewModel::ModelStatusClass::Value status() const { return m_status; } void setStatus(Jellyfin::ViewModel::ModelStatusClass::Value newStatus) { m_status = newStatus; emit statusChanged(); } ... }
EDIT, you also have to register the new type:
qRegisterMetaType<Jellyfin::ViewModel::ModelStatusClass::Value>("Jellyfin::ViewModel::ModelStatusClass::Value");
-
@HenkKalkwater Maybe I am wrong, but AFAIK you should always use full namespace in header to ensure Qt introspection for SIGNALS/SLOTS/QML works.
So I would suggest you to change your headers to:
namespace Jellyfin { namespace ViewModel { class ModelStatusTest : public QObject { Q_OBJECT public: explicit ModelStatusTest(QObject *parent = nullptr) : QObject(parent) { m_timer.setInterval(500); connect(&m_timer, &QTimer::timeout, this, &ModelStatusTest::rotateStatus); m_timer.setSingleShot(false); m_timer.start(); } Q_PROPERTY(Jellyfin::ViewModel::ModelStatus status READ status WRITE setStatus NOTIFY statusChanged) ... }
-
@KroMignon Sadly, that does not seem to solve the issue for me, even after a clean build. I still get the exact same warnings from Qt and GammaRay produces the same information about the meta types and -objects.
-
@HenkKalkwater said in QML: QMetaProperty::read unable to handle for some enums defined in C++, but not all:
Sadly, that does not seem to solve the issue for me, even after a clean build. I still get the exact same warnings from Qt and GammaRay produces the same information about the meta types and -objects.
Sorry, my failure. I was a little lost in your code.
The problem I see is that
ModelStatus
is not defined on C++ side.
I can only see a declaration on QML side:qmlRegisterUncreatableType<ViewModel::ModelStatusClass>(uri, 1, 0, "ModelStatus", "Is an enum");
EDIT If I am not totally wrong what you have called
ModelStatus
is in factJellyfin::ViewModel::ModelStatusClass::Value
-
@KroMignon I see that I accidentally forgot to add a line to the snippet, but
registerTypes()
callsqmlRegisterType<ViewModel::ModelStatusTest>(uri, 1, 0, "ModelStatusTest");
as well. I´ve updated my first post to add this.ModelStatus and ModelStatusTest are both in the same header file, so they know about each other.
If I am not totally wrong what you have called ModelStatus is in fact Jellyfin::ViewModel::ModelStatusClass::Value
That´s true. After the definition of
ModelStatusClass
, there is a line withusing ModelStatus = ModelStatusClass::Value;
, which also should be included within my snippet. -
@HenkKalkwater said in QML: QMetaProperty::read unable to handle for some enums defined in C++, but not all:
That´s true. After the definition of ModelStatusClass, there is a line with using ModelStatus = ModelStatusClass::Value;, which also should be included within my snippet.
Maybe, but Qt uses introspection to find slot.
This introspection is based on string comparisons, which uses full namespaces.
This is why using namespaces with QML or SIGNALS/SLOTS is possible, but you have to be very clean!In fact writing
using ModelStatus = ModelStatusClass::Value;
makes the C++ compiler happy, but the QMeta is not aware about this!
If you want to use namespace in combination with QML/SIGNALS/SLOTS, then always use complete namespaces for all signals/slots/members signatures in header to ensure introspection will work.I am pretty sure this will solve your issue:
namespace Jellyfin { namespace ViewModel { class ModelStatusTest : public QObject { Q_OBJECT public: explicit ModelStatusTest(QObject *parent = nullptr) : QObject(parent) { m_timer.setInterval(500); connect(&m_timer, &QTimer::timeout, this, &ModelStatusTest::rotateStatus); m_timer.setSingleShot(false); m_timer.start(); } Q_PROPERTY(Jellyfin::ViewModel::ModelStatusClass::Value status READ status WRITE setStatus NOTIFY statusChanged) Jellyfin::ViewModel::ModelStatusClass::Value status() const { return m_status; } void setStatus(Jellyfin::ViewModel::ModelStatusClass::Value newStatus) { m_status = newStatus; emit statusChanged(); } ... }
EDIT, you also have to register the new type:
qRegisterMetaType<Jellyfin::ViewModel::ModelStatusClass::Value>("Jellyfin::ViewModel::ModelStatusClass::Value");
-
@KroMignon Just changing the Q_PROPERTY type to
Jellyfin::ViewModel::ModelStatusClass::Value
seems to solve the issue. making the getter/setter methods function types that verbose does not seem to be needed.It also seems to be somewhat documented: https://doc.qt.io/qt-5/moc.html#enums-and-typedefs-must-be-fully-qualified-for-signal-and-slot-parameters. I should´ve read the documentation better :)
Anyways, a huge thank you for your help. I´ve been stuck on this for about 2 days.
-
@HenkKalkwater said in QML: QMetaProperty::read unable to handle for some enums defined in C++, but not all:
Anyways, a huge thank you for your help. I´ve been stuck on this for about 2 days.
Your welcome, I am glad I could help you :)