Qt and QML maps

Qt and QML maps

Einführung

Eine der größten Abteilungen in unserem Unternehmen ist die Entwicklung von C++-Anwendungen mit Hilfe der bekannten Qt-Bibliothek. Die meisten unserer Anwendungen basieren auf QWidget-Klassen und UI-Design. Seit kurzem stehen wir jedoch vor dem Problem, QML-Klassen und -Komponenten einzubinden und zu verwenden.

QML (Qt Modeling Language) ist eine „Markup Language“ für Benutzeroberflächen. Es handelt sich um eine deklarative Sprache (ähnlich wie CSS und JSON) für die Gestaltung von Anwendungen, die sich auf die Benutzeroberfläche konzentrieren. Inline-JavaScript-Code behandelt die zwingenden Aspekte. Es ist mit Qt Quick verbunden, dem ursprünglich von Nokia im Rahmen des Qt-Frameworks entwickelten UI Creation Kit. Qt Quick wird für mobile Anwendungen verwendet, bei denen Berührungseingabe, flüssige Animationen und Benutzerfreundlichkeit entscheidend sind. Ein QML-Dokument beschreibt einen hierarchischen Objektbaum. Zu den mit Qt ausgelieferten QML-Modulen gehören primitive grafische Bausteine (z. B. Rechteck, Bild), Modellierungskomponenten (z. B. FolderListModel, XmlListModel), Verhaltenskomponenten (z. B. TapHandler, DragHandler, State, Transition, Animation) und komplexere Steuerelemente (z. B. Schaltfläche, Schieberegler, Schublade, Menü). Diese Elemente können kombiniert werden, um Komponenten zu erstellen, die in ihrer Komplexität von einfachen Schaltflächen und Schiebereglern bis hin zu kompletten internetfähigen Programmen reichen.
QML-Elemente können durch Standard-JavaScript sowohl inline als auch über mitgelieferte .js-Dateien erweitert werden. Die Elemente können auch nahtlos in C++-Komponenten integriert und erweitert werden, die das Qt-Framework verwenden.

https://en.wikipedia.org/wiki/QML

Wir haben QML in einer etwas ungewöhnlichen Rolle eingesetzt: Statt eine grafische Benutzeroberfläche zu erstellen, haben wir es für die Arbeit mit Karten verwendet.

Werfen wir einen Blick auf die wichtigsten technischen Punkte und Methoden, die wir verwendet haben. Hier werden wir einige Beispiele und Grundlagen für die Interaktion zwischen unserem Qt-Code und QML-Objekten demonstrieren.

Wo speichern?

QML-Klassen und -Funktionen werden üblicherweise in Dateien mit der Erweiterung .qml gespeichert. Sie können entweder neben dem Programm oder irgendwo auf Ihrem Server abgelegt werden. Sie können sie auch über das Netzwerk laden. Wir betten unsere QMLs direkt in Ressourcen ein. Das heißt, sie sind in die ausführbare Datei eingebettet und können nicht manipuliert werden.

Qt and QML maps

Um eine solche „.qml“ zu laden, verwenden Sie die folgende Zeichenfolge:
setSource(QUrl::fromLocalFile(„:/map“))

Wie man QML einbettet

Zu Beginn mussten wir einen QML-Viewer in eines der Fenster in unserer Anwendung einbetten. Wir verwendeten den folgenden Code:

_qml_view = new QQuickView();
QWidget* container = QWidget::createWindowContainer(_qml_view, this);
container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
_ui->qml_canvas_layout->addWidget(container);
_qml_view->setSource(QUrl::fromLocalFile(":/map"));

Werfen wir einen Blick auf ein sehr einfaches Beispiel für ein QML-Objekt:

import QtQuick 2.0

Rectangle {
id: main
color: "#FF0000"
}

Man kann hier nichts sehen, bis wir die Abmessungen für das Rechteck angeben. Nachdem wir die Attribute Breite und Höhe hinzugefügt haben, erhalten wir etwas wie dieses:

QtQuick 2.0 importieren

import QtQuick 2.0

Rectangle {
id: main
color: "#FF0000"
width: 200
height: 100
}

Bingo! Ein einfaches Beispiel hat sich bewährt. Nun möchten wir diesen „Hintergrund“ unserer zukünftigen Komponente erweitern, damit er die gesamte Fläche einnimmt. Schließlich kann unser QMainWidget-Fenster seine Größe dynamisch ändern, was bedeutet, dass sich auch die Größe des dem QML-Objekt zugewiesenen Bereichs ändern wird. An dieser Stelle kommen uns die Attribute der übergeordneten Komponente zu Hilfe.

QtQuick 2.0 importieren

import QtQuick 2.0

Rectangle {
id: main
color: "#FF0000"
width: parent.width
height: parent.height
}

Qt and QML maps

Ausgezeichnet! Das ist es, was wir brauchen!

Map plugin

QML kann verschiedene vorgefertigte Bibliotheken in Form von Plugins enthalten. In unserem Fall verwenden wir ein Plugin für die Kartendarstellung, das in die Qt-Bibliothek integriert ist.

import QtQuick 2.0
import QtLocation 5.11
import QtPositioning 5.11

Rectangle {
id: main
width: parent.width
height: parent.height

Plugin {
id: mapPlugin
name: "esri"
}

Map {
id: mapView
objectName: “mapView”
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(59.9386, 30.3141)
zoomLevel: 15
}
}

Qt and QML maps

Jetzt ist es möglich, mit diesem QML-Objekt zu interagieren.

Wie wir die Karte bearbeiten können

Wir können die Parameter der Karte auslesen und sie nach Belieben ändern. Im Folgenden finden Sie ein Codebeispiel für das Auslesen der Koordinaten des Zentrums und das Setzen eines neuen Zentrums. Um aus dem Programmcode heraus auf QML-Objekte zuzugreifen, verwenden wir ihre „Namen“, die in objectName angegeben sind, und arbeiten über die Schnittstelle QQuickItem.

auto map_view = _qml_view->rootObject()->findChild<QQuickItem*>("mapView");
auto coordinates = map_view->property("center").value<QGeoCoordinate>();
coordinates.setLatitude(coordinates.latitude() + 0.02);
map_view->setProperty("center", QVariant::fromValue<QGeoCoordinate>(coordinates));

Qt and QML maps

Wir können in QML beschriebene Funktionen aufrufen. Wir wollen dies am Beispiel der Koordinaten des Mauszeigers zeigen.

Um die Koordinaten zu erhalten, fügen wir eine Struktur und eine Methode zu QML hinzu:

MouseArea {
id: mapViewMouseArea
anchors.fill: parent
propagateComposedEvents: true
hoverEnabled: true
}

function getMousePosition() {
return mapView.toCoordinate(Qt.point(mapViewMouseArea.mouseX, mapViewMouseArea.mouseY));
}

Um getMousePosition in C++ aufzurufen, verwenden wir QMetaObject::invokeMethod:

std::tuple<float, float> MainWindow::getMouseCoordinates()
{
auto map_view = _qml_view->rootObject()->findChild<QQuickItem*>("mapView");
QVariant result;
bool invoke_result = QMetaObject::invokeMethod(map_view, "getMousePosition", Qt::DirectConnection, Q_RETURN_ARG(QVariant, result));
if(!invoke_result)
std::make_tuple(0.f, 0.f);
QGeoCoordinate coordinates = result.value<QGeoCoordinate>();
return std::make_tuple(coordinates.latitude(), coordinates.longitude());
}

Qt and QML maps

Jetzt werden wir demonstrieren, wie man Daten von QML zu QWidgets überträgt. Dazu haben wir ein Center Change Event zu QML hinzugefügt und rufen eine Methode eines Objekts darin auf:

onCenterChanged: {
qmlReceiver.centerChanged(center);
}

Wir können das Objekt in der „.cpp“-Datei beschreiben und es mit QML verknüpfen:

class QMLReceiver : public QObject
{
private:
Q_OBJECT

public:
Q_INVOKABLE void centerChanged(QGeoCoordinate coordinate)
{
emit centerChangedSignal(coordinate.latitude(), coordinate.longitude());
}

signals:
void centerChangedSignal(float lat, float lon);
};

_qml_receiver = new QMLReceiver();
_qml_view->rootContext()->setContextProperty("qmlReceiver", _qml_receiver);
QObject::connect(_qml_receiver, &QMLReceiver::centerChangedSignal, this, &MainWindow::onCenterChanged);

Achten Sie auf die Definition der Methode, die von QML aus aufgerufen wird, und auf die Bindung der Objekte über setContextProperty.

Qt and QML maps

Kommen wir nun zum interessantesten Punkt, nämlich der Platzierung von Objekten auf der Karte.

Um Objekte auf der Karte darzustellen, verwenden wir das Model-View-Konzept. Fügen wir dies dem Map-Objekt hinzu:

MapItemView {
model: markerModel
delegate: mapComponent
}

Dabei ist markerModel eine С++-Klasse der Klasse MarkerModel final : public QAbstractListModel, die mit QML auf die gleiche Weise verbunden ist wie der frühere Event-Listener _qml_view->rootContext()->setContextProperty(„markerModel“, _model). Damit wird mapComponent wie folgt beschrieben:

Component {
id: mapComponent

MapQuickItem
{
id: marker
anchorPoint.x: image.width/2
anchorPoint.y: image.height
coordinate: positionValue

property string identifier: identifierValue
property string name: nameValue
property string icon: iconValue

sourceItem: Image {
id: image
source: icon;
}

MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
drag.target: parent

onClicked: {
qmlReceiver.markerClicked(identifier, name, coordinate);
}
}
}

Hier sehen Sie anpassbare Eigenschaften, die Daten enthalten, die wir mit unserem Modell verknüpfen müssen:

  • positionValue, von dem die Koordinaten des Objekts übernommen werden;
  • identifierValue und nameValue, die es uns ermöglichen, das Objekt zu identifizieren;
  • iconValue, wo sich das Symbol des Objekts befindet.

Auf diese Weise haben wir die Eigenschaften mit dem Modell verknüpft:

enum MarkerRoles
{
positionRole = Qt::UserRole + 1,
identifierRole = Qt::UserRole + 2,
nameRole = Qt::UserRole + 3,
iconRole = Qt::UserRole + 4,
};

QHash<int, QByteArray> MarkerModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[positionRole] = "positionValue";
roles[identifierRole] = "identifierValue";
roles[nameRole] = "nameValue";
roles[iconRole] = "iconValue";
return roles;
}

QVariant MarkerModel::data(const QModelIndex& index, int role) const
{
if(index.row() < 0 ||
index.row() >= _markers.count())
return QVariant();
if (role == MarkerModel::positionRole)
return QVariant::fromValue(_markers[index.row()]._position);
else if (role == MarkerModel::identifierRole)
return QVariant::fromValue(_markers[index.row()]._identifier);
else if (role == MarkerModel::nameRole)
return QVariant::fromValue(_markers[index.row()]._name);
else if(role == MarkerModel::iconRole)
return QVariant::fromValue(_markers[index.row()]._icon);
return QVariant();
}

Dabei ist _markers ein Array von Markern mit den erforderlichen Parametern.

Qt and QML maps

Sie können Marker hinzufügen oder löschen, sie auf der Karte verschieben, ihre Parameter abfragen und Antworten von ihnen erhalten (z. B. auf einen Mausklick). Das Einzige, was uns Grenzen setzt, ist unsere Fantasie.

Wichtig zu erwähnen: In unserem Beispiel haben wir Icons aus dem Internet verwendet, aber Sie können jederzeit Ihre lokalen Dateien verwenden. Beachten Sie, dass Sie keine Datei mit demselben Namen, aber anderem Inhalt verwenden können. Damit sich das Symbol auf der Karte ändert, muss auch der Dateiname anders sein.

Sie können das Quellarchiv herunterladen, indem Sie auf den folgenden Link klicken.

IHR TERMIN MIT UNS

Rufen Sie uns an +49 (6283) 3031157 um einen Termin zu vereinbaren.