Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

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 its statusproperty 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 to JModelStatus, as maybe the name ModelStatus clashed with something already within the Qt library.
    • I´ve tried renaming ModelStatus::Value to ModelStatus::ModelStatusValue as maybe having Q_ENUM(Value) in two differend Q_GADGETs 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 flags MovableType, WasDeclaredAsMetaType and Jellyfin::DTO::GeneralCommandTypeClass::Value with the flags MovableType, IsEnumeration, WasDeclaredAsMetaType, but for the ModelStatus it only shows Jellyfin::ViewModel::ModelStatus*with the flags MovableType, WasDeclaredAsMetaType. It does not show the Jellyfin::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 the ModelStatus 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



  • @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 fact Jellyfin::ViewModel::ModelStatusClass::Value



  • @KroMignon I see that I accidentally forgot to add a line to the snippet, but registerTypes() calls qmlRegisterType<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 with using 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::Valueseems 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 :)