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!
-
@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.
-
@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... :(
-
@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.
-
@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. -
@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.
-
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. :)