Issue
I’m working on an open street map feature with QML and Qt 5.11.
I just wrote a minimal reproduce case. It consists in displaying one blue dot on the map. When I change map zoom with the mouse wheel, scale factor is computed and when this scale is greater than 0.1, the blue dot should be displayed, else not.
In my map component, I’ve created a scale property which is an alias to a scaleFactor() function.
In my map, I use MapItemView which contains a MapQuickItem as a delegate, and this MapQuickItem contains a Rectangle which contains a Canvas.
My first idea was to add a property alias inside my Rectangle to get an onScaleChanged event, as this:
property alias scale: map.scale
onScaleChanged: {
myCanvas.requestPaint();
}
But this does not work, as the error message in the qmlscene tool is:
Line 51 Invalid alias reference. Unable to find id "map".
I’m pretty sure the problem is because I’m in a MapItemView, but I can’t find any information about that.
So any help is greatly appreciated.
Content on myfile.qml
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtLocation 5.11
import QtPositioning 5.11
import QtQuick.Window 2.11
Window {
id: root; width: 800; height: 600;
Plugin { id: mapPlugin; name: "osm"; }
ListModel {
id: myModel
ListElement { latitude: 48.2351164; longitude: 6.8986936; }
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(48.2351164, 6.8986936)
zoomLevel: 19
property real scale: map.scaleFactor()
onScaleChanged: {
console.log(">>> scale from map component", scale);
}
// function reimplemented from qdeclarativegeomapquickitem.cpp
function scaleFactor() {
return Math.pow(0.5, map.maximumZoomLevel - map.zoomLevel);
}
MapItemView {
model: myModel
delegate: MapQuickItem {
anchorPoint.x: myRect.width / 2
anchorPoint.y: myRect.height / 2
width: myRect.width
height: myRect.height
zoomLevel: map.maximumZoomLevel // set the zoomLevel (1:1 scale)
coordinate: QtPositioning.coordinate(model.latitude, model.longitude)
sourceItem: Rectangle {
id: myRect
readonly property int radius: 50
property alias scale: map.scale
onScaleChanged: {
myCanvas.requestPaint();
}
width: radius * 2
height: radius * 2
color: "transparent"
Canvas {
id: myCanvas
anchors.fill: parent
onPaint: {
var width = myRect.width;
var height = myRect.height;
var centreX = width / 2;
var centreY = height / 2;
var ctx = getContext("2d");
ctx.reset();
if (scale > 0.1) {
ctx.fillStyle = "blue";
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.moveTo(centreX, centreY);
ctx.arc(centreX, centreY, myRect.radius, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
}
}
}
}
}
}
}
}
Just run qmlscene myfile.qml to execute.
Solution
Working code below.
The issue is that while the MapQuickItem appears to be in the namespace of your myfile.qml component, it actually is not. Delegates are actually components themselves and start a new namespace. You can think of delegates ending up in a new anonymous component that cannot refer to ids in the surrounding component it appears in.
However, while it can’t refer to ids in the surrounding component, it can refer to component ids in the visual hierarchy of components. So, you can refer to root (the id of this file’s component itself) in the hierarchy of components that were instantiated when this visual hierarchy is built at run time.
From there, you can advertise the Map component which is private to this component on the root object as an alias and then refer to it via the root reference in MapItemView.
It’s all a bit confusing at first but makes sense when you start to realize that components are clusters of QML defined by files, Components, or delegate: definitions. Intra-component id references are fine but external references have to either be passed down as properties or in delegate situations reference through the hierarchy of components upward. And then a child QML object of a component must be advertised as an alias to give external access.
More info here:
https://doc.qt.io/qt-5/qtqml-documents-scope.html
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtLocation 5.11
import QtPositioning 5.11
import QtQuick.Window 2.11
Window {
id: root; width: 800; height: 600;
visible: true
property alias map: map
Plugin { id: mapPlugin; name: "osm"; }
ListModel {
id: myModel
ListElement { latitude: 48.2351164; longitude: 6.8986936; }
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(48.2351164, 6.8986936)
zoomLevel: 19
property real scale: map.scaleFactor()
onScaleChanged: {
console.log(">>> scale from map component", scale);
}
// function reimplemented from qdeclarativegeomapquickitem.cpp
function scaleFactor() {
return Math.pow(0.5, map.maximumZoomLevel - map.zoomLevel);
}
MapItemView {
model: myModel
delegate: MapQuickItem {
anchorPoint.x: myRect.width / 2
anchorPoint.y: myRect.height / 2
width: myRect.width
height: myRect.height
zoomLevel: map.maximumZoomLevel // set the zoomLevel (1:1 scale)
coordinate: QtPositioning.coordinate(model.latitude, model.longitude)
sourceItem: Rectangle {
id: myRect
readonly property int radius: 50
property real scale: root.map.scale
onScaleChanged: {
myCanvas.requestPaint();
}
width: radius * 2
height: radius * 2
color: "transparent"
Canvas {
id: myCanvas
anchors.fill: parent
onPaint: {
var width = myRect.width;
var height = myRect.height;
var centreX = width / 2;
var centreY = height / 2;
var ctx = getContext("2d");
ctx.reset();
if (scale > 0.1) {
ctx.fillStyle = "blue";
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.moveTo(centreX, centreY);
ctx.arc(centreX, centreY, myRect.radius, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
}
}
}
}
}
}
}
}
Answered By – David K. Hess
Answer Checked By – Terry (BugsFixing Volunteer)