📜 ⬆️ ⬇️

Linking a data model in C ++ with a QML view using a map example



This post participates in the competition "Smart phones for smart posts" .

Let's try to solve the following problem: to show in the application a map with pins, that is, to make the standard functionality necessary for any LBS application. Moreover, to do this in the MVC paradigm - that is, the data model and controller in C ++, and QML deals only with the display and logic associated with the UI. For the map, we will use the standard Map element, and at the same time we will deal with the binding of the data model from C ++ and QML.

')
To work, you need the installed Qt SDK 1.1.4. We create a new project: Qt Quick Application -> Qt Quick Components for Meego / Harmattan, then everything is by default. Since we will be using Qt Mobilitiy, we need to add to the pro file:

CONFIG += mobility 


After that we have an empty project with some binding in C ++ and two qml files - main.qml and MainPage.qml. We will not touch the Main, it contains the standard for the platform, the upper status bar and the lower menu. In MainPage you need to import the necessary:

 import QtMobility.location 1.2 


and delete everything inside the Page element, replacing it with a Map:

 Map { id: map anchors.fill:parent plugin: Plugin {name:"nokia"} zoomLevel: 13 center: Coordinate { latitude: 55.755786; longitude: 37.617633 } } 


I’ll draw your attention to the definition of Plugin - you just have to write it in order to get Nokia (Ovi) Maps tiles in the map. It is possible to connect other plug-ins, but they are not out of the box. Next, we determine the level of approximation and coordinates of the center of the map. We start, and we see Moscow.

Everything is good, but we need at least a finger scroll. The native control cannot do this, so we do it ourselves. On top of the map, add a MouseArea that will catch single tachi and move the map:

  MouseArea { anchors.fill:parent property int lastX : -1 property int lastY : -1 onPressed : { lastX = mouse.x; lastY = mouse.y; } onReleased : { lastX = -1; lastY = -1; } onPositionChanged: { if (lastX>=0) { map.pan(lastX- mouse.x, lastY - mouse.y) lastX = mouse.x lastY = mouse.y } } } 


The zoom with two fingers can be sorted out in a similar way using the PinchArea element.

Now you need to draw pins. The map control supports rendering various elements in itself, both graphically primitives and bitmaps, but this can be done dynamically through the addMapObject and removeMapObject methods, which is not very consistent with our desire to do MVC. Another option is to use MapObjectView, but as the documentation says “For model data, is currently only LandmarkModel is supported. Using other types of models results in undefined behavior. ”And the LandmarkModel is a dark forest with a bunch of restrictions and unnecessary actions. We need something simpler. So we will go our own way, but first we will deal with the model in C ++.

The model will be simple: inside there will be a list of pins, each lat, lng and imageSource for url pictures for pin. But in the view, we will not give the lat and lng anymore, but the absolute X and Y values ​​of the pin on the screen. We also implement the AddPin method, which will add pins to the list, and DrawPins which will be called from the view with some changes and recalculate X and Y values. DrawPins must be declared as a public slot in order to be able to receive signals from the view.

 class PinList : public QAbstractListModel //… void PinList::addPin(QString imageSource, double lat, double lng) { //      Pin *pin = new Pin(this); pin->imageSource = imageSource; pin->Lat = lat; pin->Lng = lng; beginInsertRows(QModelIndex(), this->Pins->length(), this->Pins->length()); this->Pins->append(pin); endInsertRows(); // view,    emit dataChanged(createIndex(0,0),createIndex(this->Pins->size(),0)); } //view       lat, lng   x,y (        0,0) void PinList::DrawPins(QString x,QString y,QString x_end,QString y_end, QString map_x, QString map_y) { //   map_first_lat = x.toDouble(); map_first_lng = y.toDouble(); map_second_lat = x_end.toDouble(); map_second_lng = y_end.toDouble(); map_x_end = map_x.toDouble(); map_y_end = map_y.toDouble(); // view,    emit dataChanged(createIndex(0,0),createIndex(this->Pins->size(),0)); } //  view     //     lat,lng  x,y QVariant PinList::data(const QModelIndex & index, int role) const { if (index.row() < 0 || index.row() > this->Pins->count()) return QVariant(); const Pin* pin = this->Pins->at(index.row()); QVariant result; switch (role) { case ImageSource: result = QVariant(pin->imageSource); break; case X: if ( pin->Lng > map_first_lng && pin->Lng < map_second_lng) { result =QVariant((map_x_end)*(pin->Lng - map_first_lng)/(map_second_lng - map_first_lng)); } break; case Y: if ( pin->Lat > map_second_lat && pin->Lat < map_first_lat) { result = QVariant(map_y_end*(pin->Lat - map_first_lat)/(map_second_lat - map_first_lat)); } break; } return result; } 


Now you need to draw all this in view. To do this, define what the pin looks like:

  // Component { id: landmarkMapDelegate Item { id:land width: 20; height: 20 ; x: X y: Y Image { source:ImageSource } } } 


and draw a repeater over the map

  Item { anchors.fill:parent Repeater { model: pinlist delegate: landmarkMapDelegate } } 


Here it is important: the model is tied to the pinlist identifier. It is this identifier that will then need to be specified for model binding in C ++.

Let's declare the function DrawPins, which will consider lat and lng corners of the map, and transfer it to the model

  function drawPins() { var topLeft = map.toCoordinate(Qt.point(0,0)) var bottomRight = map.toCoordinate(Qt.point(map.width, map.height)) pinlist.DrawPins(topLeft.latitude,topLeft.longitude,bottomRight.latitude,bottomRight.longitude, map.width,map.height); } 


And call it on the OnPositionChanges card.

Now it remains, in fact, to link our model and View:

  //  PinList* pinList = new PinList(0); //   pinList->addPin("qrc:/icons/pin.png", 55.745, 37.6175); pinList->addPin("qrc:/icons/pin.png", 55.7575, 37.619697); pinList->addPin("qrc:/icons/pin.png", 55.751667, 37.617778); //    viewer->rootContext()->setContextProperty("pinlist", pinList); // qml viewer->setMainQmlFile(QLatin1String("qml/untitled/main.qml")); 

Everything.

Sources

Source: https://habr.com/ru/post/134302/


All Articles