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

QML: "Cannot assign Derived to Base"



  • Hello,

    I've been trying for 2 days to find an answer to this, but either I've been using the wrong (key)words, either there's no answer.

    I have in C++ a class 'Point' derived from QGeoCoordinate and marked with Q_GADGET (just like QGeoCoordinate). Metatype is also declared as "Q_DECLARE_METATYPE(Point)"

    When an instance of this class reaches QML (either by Q_PROPERTY of another class, or an argument of a signal), everything can be read correctly from the object (latitude, longitude from QGeoCoordinate, and other members from Point). But if I try to assign it to something that needs a QGeoCoordinate (for instance, a map.center, or a polyline point), I get a QML error on runtime: "Error: Cannot assign Point to QGeoCoordinate"

    Since Point derives QGeoCoordinate, I expected this to work. Am I doing something wrong, or this is not possible?

    Here's my sample code:
    point.h:

    #pragma once
    #include <QGeoCoordinate>
    
    class Point : public QGeoCoordinate
    {
        Q_GADGET
        Q_PROPERTY(float azimuth READ getAzimuth CONSTANT)
    
    public:
        Point()
        {
            setLatitude(25);
            setLongitude(25);
        }
    
        float getAzimuth() const { return m_azimuth; }
    
    private:
        float m_azimuth = 13.15;
    };
    
    Q_DECLARE_METATYPE(Point)
    

    controller.h

    #pragma once
    #include "point.h"
    #include <QObject>
    
    class Controller : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(Point point READ getPoint CONSTANT)
    
    public:
        Controller() { }
        Point getPoint() const { return point; }
    
    private:
        Point point;
    };
    

    main.cpp:

    #include "controller.h"
    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    
    int main(int argc, char *argv[])
    {
        QGuiApplication a(argc, argv);
        QQmlApplicationEngine engine;
        Controller controller;
    
        engine.rootContext()->setContextProperty("controller", &controller);
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
        return a.exec();
    }
    

    main.qml:

    import QtQuick 2.15
    import QtQuick.Controls 1.4
    import QtLocation 5.15
    import QtQuick.Window 2.2
    
    Window {
        visible: true
        visibility: "Maximized"
    
        Plugin {
            id: mapPlugin
            name: "esri"
        }
    
        Map {
            id: map
            anchors.fill: parent
            plugin: mapPlugin
        }
    
        Button {
            text: "clic"
            onClicked: {
                console.log(controller.point.azimuth)
                console.log(controller.point.latitude)
                console.log(controller.point.longitude)
    
    
                map.center = controller.point // => qrc:/main.qml:28: Error: Cannot assign Point to QGeoCoordinate
            }
        }
    }
    

    Thanks!


  • Moderators

    @alexcrist
    i dont see that you are registering your derived type to QML?!
    registering a extending type should be enough i think:
    https://doc.qt.io/qt-5/qtqml-cppintegration-definetypes.html#registering-extension-objects



  • @raven-worx Indeed I didn't. But the documentation states that ”An extension class is a regular QObject, with a constructor that takes a QObject pointer.” and ”This takes effect if the type is exposed to QML using a QML_ELEMENT or QML_NAMED_ELEMENT() macro.”, neither of which applies here.

    Both QGeoCoordinate and Point are Q_GADGETs, not Q_OBJECTs. Am I mistaking that QML_EXTENDED doesn't work with Q_GADGETs?

    And, as I said, the Point instance can be accessed correctly in QML, the 3 console.logs work just fine, so the type itself seems to be registered in QML. The issue is with assigning it to other QGeoCoordinate properties of other objects.


  • Moderators

    @alexcrist said in QML: "Cannot assign Derived to Base":

    And, as I said, the Point instance can be accessed correctly in QML, the 3 console.logs work just fine

    i assume it works because the properties are taken from the known QGeoCoordinate qml type.

    Does it work when you simply change the type of the qproperty definition to QGeoCoordinate?

    Q_PROPERTY(QGeoCoordinate point READ getPoint CONSTANT)
    


  • There are 3 logs there:

    console.log(controller.point.azimuth) // =>prints correctly, from Point
    console.log(controller.point.latitude) // =>prints correctly, from QGeoCoordinate (base)
    console.log(controller.point.longitude) // =>prints correctly, from QGeoCoordinate (base)
    
    map.center = controller.point // => fails with: qrc:/main.qml:28: Error: Cannot assign Point to QGeoCoordinate
    

    The first one, azimuth, belongs to the Point class, the other 2 are from QGeoCoordinate. Map.center fails

    If I change the Q_PROPERTY as you suggested (and NO other changes anywhere), the code builds properly, and the behavior changes:

    console.log(controller.point.azimuth) // => fails: prints ”undefined” (makes sense, can't access Point properties)
    console.log(controller.point.latitude) // =>prints correctly, from QGeoCoordinate
    console.log(controller.point.longitude) // =>prints correctly, from QGeoCoordinate
    
    map.center = controller.point // => works correctly!
    

    So, not quite there yet... :(


  • Moderators

    @alexcrist
    i looked into the QML internals and you cannot assign a value with a custom type to a property (QObject pointers are a special case - and some others)

    For your case i suggest you either subclass Point from QObject wich returns a QGeoCoordinate in a property or invokable method (e.g. toGeoCoordinate()), or the controller returns a copy of your Point as a QGeoCoordinate.

    Either way you cannot subclass all value types and assing them to a property expecting a specific type, since the registered meta-type id is used for comparison. Thus the Point meta-type cannot be "casted" to the QGeoCoordinate meta type.


  • Qt Champions 2018

    @raven-worx said in QML: "Cannot assign Derived to Base":

    i looked into the QML internals and you cannot assign a value with a custom type to a property (QObject pointers are a special case - and some others)

    What does that refer to? Assigning a to a custom Q_GADGET property?
    Because you can do that.


  • Moderators

    @GrecKo
    depending on the type of declared property.
    the meta type system doesnt check if a metatype derives from another metatype.

    in this case the type of the property is QGeoCoordinate, and only accepts values of this type and no others.

    https://code.qt.io/cgit/qt/qtdeclarative.git/tree/src/qml/jsruntime/qv4qobjectwrapper.cpp?h=v5.14.0#n505



  • Well, that's too bad. :(

    Thanks for your help. I've applied a workaround like this in Point:

    Q_PROPERTY(QGeoCoordinate coordinates READ coordinates CONSTANT)
    const QGeoCoordinate &coordinates() const { return *this; }
    

    And seems to do the job. It's not ideal, since I also have a QList<Point> (used in a Repeater in QML), and there I need to make a copy of the list with casted objects. But if that's the only way it can work now... so be it. :)


Log in to reply