offline interactive OpenStreetMap in Qt - c++

How to display a OpenStreetMap on a ui-form in Qt? In the main window of mainwindow.ui. I need an interactive map. The transmitting latitude, longitude of the point and adding information about the point.
How to make this example interactive and display on the mainwindow.ui form?
main.cpp :
#include <QGuiApplication>
#include <QQuickView>
int main(int argc, char **argv)
{
QGuiApplication app(argc,argv);
QQuickView view;
view.setSource(QUrl(QStringLiteral("qrc:///places_map.qml")));
view.setWidth(360);
view.setHeight(640);
view.show();
return app.exec();
}
places_map.qml :
import QtQuick 2.0
import QtPositioning 5.5
import QtLocation 5.6
//! [Imports]
Rectangle {
anchors.fill: parent
//! [Initialize Plugin]
Plugin {
id: myPlugin
name: "osm" // "mapboxgl", "esri", ...
//specify plugin parameters if necessary
//PluginParameter {...}
//PluginParameter {...}
//...
}
//! [Initialize Plugin]
//! [Current Location]
PositionSource {
id: positionSource
property variant lastSearchPosition: locationOslo
active: true
updateInterval: 120000 // 2 mins
onPositionChanged: {
var currentPosition = positionSource.position.coordinate
map.center = currentPosition
var distance = currentPosition.distanceTo(lastSearchPosition)
if (distance > 500) {
// 500m from last performed pizza search
lastSearchPosition = currentPosition
searchModel.searchArea = QtPositioning.circle(currentPosition)
searchModel.update()
}
}
}
//! [Current Location]
//! [PlaceSearchModel]
property variant locationOslo: QtPositioning.coordinate( 59.93, 10.76)
PlaceSearchModel {
id: searchModel
plugin: myPlugin
searchTerm: "Pizza"
searchArea: QtPositioning.circle(locationOslo)
Component.onCompleted: update()
}
//! [PlaceSearchModel]
//! [Places MapItemView]
Map {
id: map
anchors.fill: parent
plugin: myPlugin;
center: locationOslo
zoomLevel: 13
MapItemView {
model: searchModel
delegate: MapQuickItem {
coordinate: place.location.coordinate
anchorPoint.x: image.width * 0.5
anchorPoint.y: image.height
sourceItem: Column {
Image { id: image; source: "marker.png" }
Text { text: title; font.bold: true }
}
}
}
}
//! [Places MapItemView]
Connections {
target: searchModel
onStatusChanged: {
if (searchModel.status == PlaceSearchModel.Error)
console.log(searchModel.errorString());
}
}
}

At least there are 2 ways using QtDesigner
1. using the QQuickWidget plugin from Qt Designer
Qt Designer is the tool used by QtCreator to modify the .ui, so it has more used widgets plugins, in my case I have it as shown below
and you go to the view of properties and in source you place the url of the .qml as it shows the following image:
Then we add to the .pro:
QT += quickwidgets
2. If you do not have it, we can use the widget and promote it as a class that inherits from QQuickWidget
for this we create a class that inherits from QQuickWidget:
#ifndef QUICKWIDGET_H
#define QUICKWIDGET_H
#include <QQuickWidget>
class QuickWidget: public QQuickWidget
{
public:
QuickWidget(QWidget *parent = Q_NULLPTR):QQuickWidget(parent){
setSource(QUrl("qrc:/places_map.qml"));
setResizeMode(QQuickWidget::SizeRootObjectToView);
}
};
#endif // QUICKWIDGET_H
Then we drag the QtDesigner Widget
and we right click selecting Promote to..., after that we obtain a menu where we place the values shown in the image, press the add button and then the promote button:
Then we add to the .pro:
QT += quickwidgets
Both examples are in the following link

Related

Qt control OSM location from C++

In my Qt5.9 widget application project (Windows), I added a QQuickWidget in the ui and set the source file to a QML file.
My itention is to display open street maps in the QQuickWidget. By clicking a button, the center location of the map should change to specific lat/long coordinates.
The map gets displayed in the QQuickWidget as expected, however, I can't get the location change by button click to work.
I am using this QML file content to display the map:
//================================
// map.qml
//================================
import QtQuick 2.0
import QtQuick.Window 2.0
import QtLocation 5.6
import QtPositioning 5.6
Item {
id: qmlMap
Plugin {
id: osmPlugin
name: "osm"
}
Map {
id: map
anchors.fill: parent
plugin: osmPlugin
center: QtPositioning.coordinate(59.91, 10.75)
zoomLevel: 10
objectName: "mainMap"
MapQuickItem {
id: marker
coordinate {latitude: 59.91
longitude: 10.75}
anchorPoint.x: image.width * 0.5
anchorPoint.y: image.height
sourceItem: Image {
id: image
height: 35
width: 35
source: "geotag.png"
}
function recenter(lat,lng) {
map.clearMapItems();
marker.coordinate.latitude = lat;
marker.coordinate.longitude = lng;
map.addMapItem(marker);
map.center.latitude = lat;
map.center.longitude = lng;
map.update();
}
}
}
}
On application start up, I can see the OSM centered on my specified location and I also can see the marker at the right location.
Loaded map on start up
However, when I click my button to call the function recenter(lat,lng) from C++, nothing seems to happen (no location change on map visible).
My C++ button code for location change is:
void mapproject::on_btnUpdatePos_clicked()
{
QQmlEngine engine;
QQmlComponent component(&engine, "qrc:/map.qml");
QObject *object = component.create();
QVariant returnedValue;
QVariant pos = QVariant(0);
if(object != NULL){
QMetaObject::invokeMethod(object, "recenter",
Q_RETURN_ARG(QVariant, returnedValue),
Q_ARG(QVariant, pos),
Q_ARG(QVariant, pos));
}
}
Why does the location change not work? Is there a mistake in my QML file or in my C++ code?
Assuming that the QQuickWidget has been added through Qt Designer and it is called quickWidget, so you can access it using ui->quickWidget.
To do a simple search you can set an objectName in the MapQuickItem:
MapQuickItem {
id: marker
objectName: "mapItem"
coordinate {latitude: 59.91
[...]
You should not create a new component, you should use the QQuickWidget, the first thing is to get the item that shows the QQuickWidget through the rootObject() method, then look for the child named mapItem and invoke the recenter method:
void MainWindow::on_btnUpdatePos_clicked()
{
QQuickItem *item = ui->quickWidget->rootObject();
QObject *object = item->findChild<QObject*>("mapItem");
QVariant posx = QVariant(-12.0464);
QVariant posy = QVariant(-77.0428);
if(object != NULL){
QMetaObject::invokeMethod(object, "recenter",
Q_ARG(QVariant, posx),
Q_ARG(QVariant, posy));
}
}
The complete example can be found in the following link

QML. How to change MapPolyline path from C++?

Here is a QML map:
Map {
anchors.fill: parent
plugin: osmMapPlugin
center: QtPositioning.coordinate(56.006355, 92.860984)
zoomLevel: 14
MapPolyline {
line.width: 3
line.color: 'red'
path: [
{ latitude: -27, longitude: 153.0 },
{ latitude: -27, longitude: 154.1 },
{ latitude: -28, longitude: 153.5 },
{ latitude: -29, longitude: 153.5 }
]
}
}
How to change path from C++/qt? I tried this:
QML:
Map {
anchors.fill: parent
plugin: osmMapPlugin
center: QtPositioning.coordinate(56.006355, 92.860984)
zoomLevel: 14
MapPolyline {
line.width: 3
line.color: 'red'
path: map_path
}
}
C++:
map = new QQuickWidget();
map->setSource(QUrl("qrc:map.qml"));
QQmlContext *qml_map = map->rootContext();
QGeoPath *path = new QGeoPath();
path->addCoordinate(*(new QGeoCoordinate(56.0831528053, 92.8405031454)));
path->addCoordinate(*(new QGeoCoordinate(56.1, 93)));
qml_map->setContextProperty("map_path", path);
But I got a exception:
calling a private constructor of class 'QVariant':
qml_map->setContextProperty("map_path", path);
and
attempted to use of deleted function:
qml_map->setContextProperty("map_path", path);
UPD:
#hi-im-frogatto suggested that it was necessary to do so:
qml_map->setContextProperty("map_path", QVariant::fromValue(path));
It helped, the error no longer occurs. But the line is not drawn. Got error:
qrc:map.qml:30: ReferenceError: map_path is not defined
qrc:map.qml:21:5: QML Map: Plugin is a write-once property, and cannot be set again.
As it says #HiI'mFrogatto you should use QVariant::fromValue(), but it can not be used directly on the .qml side:
QGeoPath geopath;
geopath.addCoordinate(QGeoCoordinate(56.006355, 92.860984));
geopath.addCoordinate(QGeoCoordinate(56.1, 93));
geopath.addCoordinate(QGeoCoordinate(56.1, 92.777));
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("geopath", QVariant::fromValue(geopath));
what you have to do is access each element through a loop:
Map {
anchors.fill: parent
plugin: osmMapPlugin
center: QtPositioning.coordinate(56.006355, 92.860984)
zoomLevel: 10
MapPolyline {
id: pl
line.width: 3
line.color: 'red'
}
Component.onCompleted: {
var lines = []
for(var i=0; i < geopath.size(); i++){
lines[i] = geopath.coordinateAt(i)
}
pl.path = lines
}
}
But with this case we can not update the values of the QGeoPath, it is appropriate to implement a class that inherits from QObject and has as property to QGeoPath, since you can manipulate it from C++ or from QML.
main.cpp
#include <QGeoPath>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QTimer>
class PathController: public QObject{
Q_OBJECT
Q_PROPERTY(QGeoPath geopath READ geoPath WRITE setGeoPath NOTIFY geopathChanged)
public:
PathController(QObject *parent=0):QObject(parent){}
void test(){
mGeoPath.addCoordinate(QGeoCoordinate(56.006355, 92.860984));
mGeoPath.addCoordinate(QGeoCoordinate(56.1, 93));
mGeoPath.addCoordinate(QGeoCoordinate(56.1, 92.777));
QTimer *timer = new QTimer(this);
QObject::connect(timer, &QTimer::timeout, [this](){
mGeoPath.translate(0.001, -0.01);
emit geopathChanged();
});
timer->start(1000);
}
QGeoPath geoPath() const{return mGeoPath;}
void setGeoPath(const QGeoPath &geoPath){
if(geoPath == mGeoPath)
return;
mGeoPath = geoPath;
emit geopathChanged();
}
signals:
void geopathChanged();
private:
QGeoPath mGeoPath;
};
int main(int argc, char *argv[])
{
#if defined(Q_OS_WIN)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
PathController controller;
controller.test();
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("pathController", &controller);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtLocation 5.6
import QtPositioning 5.6
Window {
visible: true
width: 640
height: 480
Plugin {
id: osmMapPlugin
name: "osm"
}
Map {
anchors.fill: parent
plugin: osmMapPlugin
center: QtPositioning.coordinate(56.006355, 92.860984)
zoomLevel: 10
MapPolyline {
id: pl
line.width: 10
line.color: 'red'
}
}
function loadPath(){
var lines = []
for(var i=0; i < pathController.geopath.size(); i++){
lines[i] = pathController.geopath.coordinateAt(i)
}
return lines;
}
Connections{
target: pathController
onGeopathChanged: pl.path = loadPath()
}
Component.onCompleted: pl.path = loadPath()
}
In this link you will find the complete example.
QQmlContext::setContextProperty accepts a QVariant and there's no any implicit conversion from QGeoPath to QVariant. However using the following method you can do this:
qml_map->setContextProperty("map_path", QVariant::fromValue(path));
More info: http://doc.qt.io/archives/qt-5.5/positioning-cpp-qml.html
Based on the answer I found here - How to set QML MapPolyline Path Property - I wrote a simple test-case which is correct, works and, most importantly, is simpler than the accepted answer on this question.
The principle is simple - we can wrap our QGeoCoordinates in QVariants. We can add the QVariants to a QVariantList, and pass that directly to the QML MapPolyline.path property.
Even better, if we do this with a C++ class' Q_PROPERTY value, we don't even have to have QML logic to read or re-read the property when it changes.
Finally, with this method you can call setContextProperty after the QQmlWidget has loaded its source and begun displaying, which is useful if, for example, your QQmlWidget is part of a Qt Designer form file instead of added explicitly in C++.
Lets take a look at how this looks in practice - I'm going to create a class called RouteProvider which provides the path:
#ifndef ROUTEPROVIDER_H
#define ROUTEPROVIDER_H
#include <QObject>
#include <qqml.h>
#include <QMetaClassInfo>
#include <QGeoPath>
class RouteProvider : public QObject
{
Q_OBJECT
Q_PROPERTY(QVariantList path READ path NOTIFY pathUpdated)
QML_ELEMENT
public:
explicit RouteProvider(QObject *parent = nullptr);
QVariantList path();
signals:
void pathUpdated();
private:
QVariantList m_path;
};
#endif // ROUTEPROVIDER_H
To pass this class as a context property, the following needs to be true:
The class must be a subclass of QObject
This might actually be something you can skip using Q_GADGET and Q_REGISTER_METATYPE but that way lies madness
the class must use the QML_ELEMENT macro in the private part of its declaration
Note that your m_path and class functions can, in theory, be anything and do anything. The important aspects are:
The path function should return the QVariantList of QVariant::fromValue(QGeoCoordinate) values
Functions that change the path should emit the pathUpdated signal.
Looking at the .cpp file for this example is very simple:
#include "routeprovider.h"
RouteProvider::RouteProvider(QObject *parent) : QObject(parent)
{
m_path.append(QVariant::fromValue(QGeoCoordinate(51.50648, -0.12927)));
m_path.append(QVariant::fromValue(QGeoCoordinate(51.50826, -0.12599)));
}
QVariantList RouteProvider::path()
{
return m_path;
}
For the sake of this demonstration, I'm just creating a line between two coordinates and returning the path.
I'm going to create a QMainWindow with a QQmlWidget as its central widget, then set an instance of this class as a context property.
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "routeprovider.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
RouteProvider m_provider;
};
#endif // MAINWINDOW_H
To demonstrate this method still works even after the QML file has been loaded, I'm loading the QML file first, then setting the context property:
#include "mainwindow.h"
#include <QQuickWidget>
#include <QQmlContext>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
auto centralWidget = new QQuickWidget(this);
centralWidget->setSource(QUrl{"qrc:/CentralMap.qml"});
centralWidget->rootContext()->setContextProperty("routeProvider", &m_provider);
centralWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
this->setCentralWidget(centralWidget);
this->resize(400, 400);
}
MainWindow::~MainWindow()
{
}
And here's the simplest kind of QML file which still demonstrates this in action:
import QtQuick 2.0
import QtPositioning 5.12
import QtLocation 5.15
Item {
Plugin {
id: mapPlugin
name: "osm"
}
Map {
anchors.fill: parent
zoomLevel: 14
plugin: mapPlugin
MapPolyline {
id: line
path: routeProvider.path
line.width: 15
line.color: 'red'
}
}
}
Place it in a .qrc file to make it available to the program at run-time.
Finally, a default-generated main function starts our application:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Result:

Qt Create a QML Slider Sensitive to Touch Events

I am creating a game for touch screens that requires 2-4 players to each have access to a pair of slider controls. The problem is that the QML Slider control responds to touch as a mouse event and seizes the focus. Then only one player can access a single control at a time. I need multiple sliders to respond to touch events simultaneously. My question is how to do that?
With the help of a variety of stack overflow posts, I have been able to create my own answer that so far seems to work. I detail the answer below in the answer section to save other newbies like me the trouble.
There is a pure qml solution to this problem. The TouchSlider C++ object in my first answer (elsewhere in this thread) was unnecessary. Here I have modified the code to the TouchSlider qml code to eliminate references to touchslider (the TouchSlider C++ object).
TouchPoint.qml:
import QtQuick 2.0
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
Item {
property string sliderTitle
property real sliderMin
property real sliderMax
property real sliderVal
ColumnLayout{
id: column1
Label {
text: qsTr(sliderTitle)
font.pointSize: 10
}
Slider {
id: touchSlider1
minimumValue: sliderMin
maximumValue: sliderMax
orientation: Qt.Vertical
value: sliderVal
onValueChanged: function(){
sliderVal = Math.round(this.value);
labelSliderValue.text = qsTr(JSON.stringify(sliderVal));
}
}
Label {
id: labelSliderValue
text: qsTr(JSON.stringify(sliderVal))
font.pointSize: 10
}
function sliderSetValueFromTouch(position){
// Assume qs a vertical slider
var minvalue = touchSlider1.minimumValue;
var maxvalue = touchSlider1.maximumValue;
// Since this is a vertical slider, by assumption, get the height
var height = touchSlider1.height;
// Compute the new value based on position coordinate
var newvalue = (height-position)/height * (maxvalue-minvalue);
if (newvalue<minvalue) newvalue = minvalue;
if (newvalue>maxvalue) newvalue = maxvalue;
//qDebug() << newvalue;
// Set the value of the slider
touchSlider1.value = newvalue;
}
MultiPointTouchArea{
anchors.fill: touchSlider1
touchPoints: [
TouchPoint {
id: point1
onPressedChanged: function(){
if(pressed){
//console.log("pressed");
//console.log(touchslider.testStringReturn());
//touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
column1.sliderSetValueFromTouch(point1.y);
}
}
}
]
onTouchUpdated: function(){
//touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
column1.sliderSetValueFromTouch(point1.y);
}
}
}
}
The touchslider.h and touchslider.cpp files add no value.
I could not find a pure QML way to solve the problem but I wanted to minimize the use of C++. Using C++, I create an object TouchSlider and add it to my qml scene. The TouchSlider object has a simple function to update the value of a vertical slider according to a position argument. Then in the QML code, I add a MultiPointTouchArea on top of a regular slider and respond to the touch events by calling C++ function.
Here are all my files for a project called SliderPair.
SliderPair.pro:
QT += qml quick widgets
QT += quickcontrols2
QT += core
CONFIG += c++11
SOURCES += main.cpp \
touchslider.cpp
RESOURCES += \
qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH += qml
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
HEADERS += \
touchslider.h
DISTFILES +=
main.cpp:
#include <QApplication>
#include <QQmlApplicationEngine>
// add following includes for exposing new class TouchSlider to QML
#include <QQmlEngine>
#include <QQmlContext>
#include "touchslider.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
//Create an object of type TouchSlider
//When a scoped pointer goes out of scope the object is deleted from memory. Good housekeeping:
QScopedPointer<TouchSlider> touchslider (new TouchSlider);
QQmlApplicationEngine engine;
engine.addImportPath(QStringLiteral("qml"));
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
//QML can now refer to the TouchSlider object using the handle "touchslider":
engine.rootContext()->setContextProperty("touchslider",touchslider.data());
return app.exec();
}
touchslider.h:
#ifndef TOUCHSLIDER_H
#define TOUCHSLIDER_H
#include <QObject>
#include <QDebug>
#include <QtQuickControls2>
class TouchSlider : public QObject
{
Q_OBJECT
public:
explicit TouchSlider(QObject *parent = 0);
//call Q_INVOKABLE macro to set up functions for QML
Q_INVOKABLE void testDebug(); //hello world from C++
Q_INVOKABLE QString testStringReturn(); //hello world to javascript
Q_INVOKABLE void sliderSetValueFromTouch(QQuickItem *qs,int position );//use touch event to set slider value
signals:
public slots:
};
#endif // TOUCHSLIDER_H
touchslider.cpp:
#include "touchslider.h"
TouchSlider::TouchSlider(QObject *parent) : QObject(parent)
{
}
void TouchSlider::testDebug()
{
qDebug() << "Hello from C++";
}
QString TouchSlider::testStringReturn()
{
QString message = "Hi from C++";
return message;
}
void TouchSlider::sliderSetValueFromTouch(QQuickItem *qs, int position)
{
// Assume qs a vertical slider
// Get its properties (its slider properties are accessible even though it is declared as QQuickItem)
// minimumValue and maximumValue are of type QVariant so we need to cast them as double
double minvalue = qs->property("minimumValue").value<double>();
double maxvalue = qs->property("maximumValue").value<double>();
// Since this is a vertical slider, by assumption, get the height
double height = qs->property("height").value<double>();
// Compute the new value based on position coordinate
double newvalue = (height-position)/height * (maxvalue-minvalue);
if (newvalue<minvalue) newvalue = minvalue;
if (newvalue>maxvalue) newvalue = maxvalue;
//qDebug() << newvalue;
// Set the value of the slider
qs->setProperty("value",newvalue);
}
TouchSlider.qml:
import QtQuick 2.0
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
Item {
property string sliderTitle
property real sliderMin
property real sliderMax
property real sliderVal
ColumnLayout{
Label {
text: qsTr(sliderTitle)
font.pointSize: 10
}
Slider {
id: touchSlider1
minimumValue: sliderMin
maximumValue: sliderMax
orientation: Qt.Vertical
value: sliderVal
onValueChanged: function(){
sliderVal = Math.round(this.value);
labelSliderValue.text = qsTr(JSON.stringify(sliderVal));
}
}
Label {
id: labelSliderValue
text: qsTr(JSON.stringify(sliderVal))
font.pointSize: 10
}
MultiPointTouchArea{
anchors.fill: touchSlider1
touchPoints: [
TouchPoint {
id: point1
onPressedChanged: function(){
if(pressed){
//console.log("pressed");
//console.log(touchslider.testStringReturn());
touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
}
}
}
]
onTouchUpdated: function(){
touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
}
}
}
}
PlayerControls.qml:
import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
Item {
// These properties act as constants, useable outside this QML file
property string playerName
property real priceMin
property real priceMax
property real qualityMin
property real qualityMax
property real priceValue
property real qualityValue
property int sliderWidth
ColumnLayout{
id: columnLayout1
width: 640
height: 480
Layout.minimumWidth: 640
Layout.fillWidth: true
anchors.fill: parent
spacing: 10.2
Label {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
id: labelPlayer1
text: qsTr(playerName)
font.pointSize: 10
}
RowLayout{
ColumnLayout{
Label {
text: qsTr("")
font.pointSize: 10
width: 50
}
}
TouchSlider {
width: sliderWidth
sliderTitle: "Price"
sliderMin: priceMin
sliderMax: priceMax
sliderVal: priceValue
}
TouchSlider {
width: sliderWidth
sliderTitle: "Quality"
sliderMin: qualityMin
sliderMax: qualityMax
sliderVal: qualityValue
}
}
}
}
main.qml:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("SliderPair Test")
Item {
PlayerControls{
playerName: "Player 1"
priceMin: 0
priceMax: 200
priceValue: 100
qualityMin: 0
qualityMax: 50
qualityValue: 25
sliderWidth: 200
}
}
}
The result should look like this:
On a touch screen like my Surface Pro, I can control each slider simultaneously with two fingers. Since Windows supports up to 10 simultaneous touches that should mean I can have 2-4 players without a problem. We shall see.

Qt / QML set property from c++ class for GridView

First i created a new c++ class with a member function, which is giving back a string:
#ifndef TESTNAME_H
#define TESTNAME_H
#include <QObject>
#include <QString>
#include <QVariant>
class testname : public QObject
{
Q_OBJECT;
public:
testname();
Q_INVOKABLE QString getName();
};
#endif // TESTNAME_H
#include "testname.h"
testname::testname()
{
}
QString testname::getName() {
return "sometext";
}
I have a qml file with only a text in the middle, like this:
import QtQuick 1.1
Rectangle {
width: 360
height: 360
Text {
id: text1
anchors.centerIn: parent
text: testqml
font.pixelSize: 12
}
}
Notice that the property "text" is a variable named "testqml". This variable contains the string returned by the function of the class i showed above. The code for this is in the main.cpp:
#include <QApplication>
#include "qmlapplicationviewer.h"
#include <testname.h>
#include <QDeclarativeContext>
#include <QDebug>
Q_DECL_EXPORT int main(int argc, char *argv[])
{
QScopedPointer<QApplication> app(createApplication(argc, argv));
// Create instance of "testname"
testname *test = new testname;
QmlApplicationViewer viewer;
viewer.rootContext()->setContextProperty("testqml", test->getName());
viewer.addImportPath(QLatin1String("modules"));
viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
viewer.setMainQmlFile(QLatin1String("qml/classtest/main.qml"));
viewer.showExpanded();
return app->exec();
}
Using the setContextProperty-Function, the returned string is exposed to the qml file and is correctly shown in the running program.
But i'm actually intending to inherit a model for a GridView. So i created a Gridview with one list element in QML:
import QtQuick 1.1
Rectangle {
width: 360
height: 360
GridView {
id: grid_view1
anchors.centerIn: parent
width: 140
height: 140
cellHeight: 70
delegate: Item {
x: 5
height: 50
Column {
spacing: 5
Rectangle {
width: 40
height: 40
color: colorCode
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
x: 5
text: name
anchors.horizontalCenter: parent.horizontalCenter
font.bold: true
}
}
}
model: ListModel {
ListElement {
name: testqml
colorCode: "grey"
}
}
cellWidth: 70
}
}
The variable "testqml" is now in the "name" Field of the List, which is in the example a string. If i use a string "likethis", it is correctly displayed. But if i run my program (main.cpp and class are remaining unchanged) i get this error:
ListElement: cannot use script for property value
name: testqml
^
Now i'm stuck. I found an reported bug with a similar issue #QTBUG-16289, but i don't know how to solve my problem. Any ideas, how-tos, tutorials or something to solve my problem?
Thanks and regards :)
I ran your code and reproduced your error but what are you ultimately trying to do?
Your delegate indicates that you want to inject a number of model items with name and color from C++, otherwise, why use a GridView, correct?
If that is not the case then perhaps what follows will not be that useful, or perhaps some variant of it might be. So I went ahead and constructed an example of what I thought you might be trying to accomplish.
In summary, on my system, after creating an arbitrary number of model items (in this case 20), the scrollable GridView delegate (midway along the scroll range) looks like this:
As I said, it seems that you want to inject a number of QString items from a C++ model into a QML GridView, noting that using a GridView implies that you would like to have a number of items. In most cases you will want to inherit from a pre-defined Qt Model, which automatically takes care of several important details like keeping the QML views in-sync with the model, and automatically updating the views when items are removed or new ones are added.
With this in mind, the QAbstractListModel is a convenient Class from which to base your model (this is not the only option though, see the help files). At first glance, setting up this model can appear complicated, so I went ahead and defined a minimal version which I hope illustrates what you want to do.
Below is the code for the model (note: I put all of the code in the .h file so no .m file is needed). I also created some "Items" to inject into the model, for simplicity, a struct Item is used as defined below, but these could easily be instances of another suitably defined Class:
#include <QString>
#include <QColor>
#include <QDebug>
#include <QAbstractListModel>
// Create an Item so we have something to put in the model:
struct Item {
QString name;
QString color;
};
class testname : public QAbstractListModel
{
Q_OBJECT
public:
explicit testname(QObject *parent = 0) : QAbstractListModel(parent)
{
// Create some items and then add to the model:
int N = 20;
QStringList colorNames = QColor::colorNames();
Item* items = new Item[N];
for (int i = 0; i < N; i++) {
items[i].name = QString("item"+QString::number(i));
items[i].color = colorNames[i];
//qDebug() << items[i].name << "; " << items[i].color;
_model<<items[i];
}
}
// enum DataRoles for QAbstractListModel:
enum DataRoles {
NameRole = Qt::UserRole + 1,
ColorRole
};
// addData() method for QAbstractListModel:
void addData(const Item& entry) {
beginInsertRows(QModelIndex(), rowCount(), rowCount());
_model << entry;
endInsertRows();
}
// rowCount() method for QAbstractListModel:
int rowCount(const QModelIndex & parent = QModelIndex()) const {
return _model.count();
}
// data() required for QAbstractListModel:
QVariant data(const QModelIndex & index, int role) const {
if ( !index.isValid() || index.row() < 0 || index.row() >= _model.count() )
return QVariant();
Item modelEntry = _model[index.row()];
if (role == NameRole) {return modelEntry.name;}
if (role == ColorRole) {return modelEntry.color;}
return QVariant();
}
// roleNames() method for QAbstractListModel:
QHash<int,QByteArray> roleNames() const {
QHash<int, QByteArray> roles;
roles[NameRole] = "Name";
roles[ColorRole] = "Color";
return roles;
}
private:
// Below are the model items:
QList<Item> _model;
};
Next is the QML code which uses the C++ model defined above and registered as "testqml" in main.cpp, and then defined through the property, model: in GridView.
Note that in the delegate that the Color and Name properties of the model are defined as role names in the class above (these could be any label you like). To help visualize what is going on, the model roles are very similar to the columns of a table, with the row entries corresponding to the model items:
import QtQuick 1.1
Rectangle {
width: 360
height: 360
/* ------------------- */
GridView {
id: grid_view1
anchors.centerIn: parent
width: 140; height: 140
cellHeight: 70
delegate: delegateItem
model: testqml // the C++ model is set here
cellWidth: 70;
}
/* ------------------- */
Component {
id: delegateItem
Item {
x: 5; height: 50
Column {
spacing: 5
Rectangle {
width: 40; height: 40;
color: Color // Note: this a role defined in the C++ model
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
x: 5;
text: Name // Note: this is another role defined in the C++ model
anchors.horizontalCenter: parent.horizontalCenter
font.bold: true
}
}
}
} // end delegateItem
} // end Rectangle
And then my main.cpp is nearly the same as yours, I'll go ahead and post it to avoid any confusion:
#include "qtquick1applicationviewer.h"
#include <QApplication>
#include "testname.h"
#include <QDeclarativeContext>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
testname *test = new testname();
QtQuick1ApplicationViewer viewer;
viewer.rootContext()->setContextProperty("testqml",test);
viewer.addImportPath(QLatin1String("modules"));
viewer.setOrientation(QtQuick1ApplicationViewer::ScreenOrientationAuto);
viewer.setMainQmlFile(QLatin1String("qml/QMLSetProperty/main.qml"));
viewer.showExpanded();
return app.exec();
}
Hope this helps!

Complex models and displaying data

I'm just beginning to learn C++ and Qt Framework in particular and I already have a problem right there. The question is how do I create and display data which is not just a string but rather an object, which properties I can access and display. E.g I have a list of employees and I want to display a list which looks like this:
---------------------
John Smith
Salary: 50,230
---------------------
Max Mustermann
Salary: 67,000
---------------------
The goal is that each item in the list is clickable and opens a new window with the details. Also, the important part is that I can be able to style the properties differently.
Qt provide us model and view frameworks, it is pretty flexible.
You could save your data by "model", show the data of your "model" by "view"
and determine how to play your data by "delegate"
The codes of c++ is a little bit verbose, so I use qml from the document to express the idea
import QtQuick 2.1
import QtQuick.Window 2.1
import QtQuick.Controls 1.0
Rectangle {
width: 640; height: 480
//the new window
Window{
id: newWindow
width: 480; height:240
property string name: ""
property string salaryOne: ""
property string salaryTwo: ""
Rectangle{
anchors.fill: parent
Text{
id: theText
width:width; height: contentHeight
text: newWindow.name + "\nSalaryOne : " + newWindow.salaryOne + "\nSalaryTwo : " + newWindow.salaryTwo
}
Button {
id: closeWindowButton
anchors.centerIn: parent
text:"Close"
width: 98
tooltip:"Press me, to close this window again"
onClicked: newWindow.visible = false
}
}
}
ListModel {
id: salaryModel
ListElement {
name: "John Smith"
SalaryOne: 50
SalaryTwo: 230
}
ListElement {
name: "Max Mustermann"
SalaryOne: 67
SalaryTwo: 0
}
}
//this is the delegate, determine the way you want to show the data
Component {
id: salaryDelegate
Item {
width: 180; height: 40
Column {
Text { text: name }
Text { text: "Salary : " + SalaryOne + ", " + SalaryTwo }
}
MouseArea{
anchors.fill: parent
//set the value of the window and make it visible
onClicked: {
newWindow.name = model.name
newWindow.salaryOne = model.SalaryOne
newWindow.salaryTwo = model.SalaryTwo
newWindow.visible = true
view.currentIndex = index
}
}
}
}
ListView {
id: view
anchors.fill: parent
model: salaryModel
delegate: salaryDelegate
}
}
You could separate the window or ListView into different qml files, combine the power of c++ ,qml and javascript. Declarative langauge like qml is pretty good on handling UI.
c++ version
#include <memory>
#include <QApplication>
#include <QListView>
#include <QSplitter>
#include <QStandardItemModel>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QStandardItemModel model(2, 1);
model.appendRow(new QStandardItem(QString("John Smith\nSalary: %1, %2\n").arg(50).arg(230)));
model.appendRow(new QStandardItem(QString("Max Mustermann\nSalary: %1, ").arg(67) + QString("000\n")));
QSplitter splitter;
QListView *list = new QListView(&splitter);
list->setModel(&model);
splitter.addWidget(list);
splitter.show();
return a.exec();
}
Enhance them by your need, c++ version also support delegate.
You could encapsulate the QListView and open a new window when the
user click on the index(you need QItemSelectionModel to detect which
item you selected).Before you can design higly customize UI,you have
to study a lot of the model and view frameworks of Qt. Since your case
are pretty simple, default QListView and QStandardItemModel is enough.
Supplement : How to detect which index you selected?
//the type of model_selected is QItemSelectionModel*
model_selected = list->selectionModel();
connect(model_selected, SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
this, SLOT(selection_changed(QItemSelection, QItemSelection)));
void imageWindow::selection_changed(QItemSelection, QItemSelection)
{
//do what you want
}