Threaded renderer in QML - c++

Threaded renderer is not working in the following code. I'm using Qt 5.4 on Arch linux-3.14 with proprietary drives.
---------- mytext.h -----------
#include <QObject>
class Thing : public QObject {
Q_OBJECT
Q_PROPERTY(int qm_no READ qm_no NOTIFY qm_noChanged)
public:
Q_INVOKABLE void loop();
int qm_no();
signals:
void qm_noChanged();
private:
int m_no;
};
---------- mytext.cpp ----------
#include "mytext.h"
#include <unistd.h>
int Thing::qm_no() {
return m_no;
}
void Thing::loop() {
while(true) {
m_no += 1;
emit qm_noChanged();
usleep(1000000);
}
}
--------- main.cpp -----------
#include <QQmlContext>
#include <QQuickView>
#include <QGuiApplication>
#include <QtQml>
#include "mytext.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
Thing myTh;
QQuickView view;
view.rootContext()->setContextProperty("qmyTh",&myTh);
view.setSource(QUrl::fromLocalFile("main.qml"));
view.show();
return app.exec();
}
------- main.qml ----------
import QtQuick 2.0;
Rectangle {
id: root
width: 200
height: 200
property var name: "test"
Text {
anchors.fill: parent
text: name
}
MouseArea {
anchors.fill: parent
onClicked: {
qmyTh.loop()
}
}
Connections {
target:qmyTh
onQm_noChanged: {
name = qmyTh.qm_no;
}
}
}
Explanation::
There is a classes Thing , with its object myTh. The function of class Thing is to provide an invokable function which here is loop. This function will then continuously update the m_no value and emit signal. Now the question is that how can I update the Text (name property) while the infinite loop is running which keeps on updating the value to be displayed ?

The code is correct for what concerns the QML part (now) and it works correctly. What is not correct is the C++ implementation. if you remove the while loop, leaving its content, and execute your code you'll see that the text is correctly updated.
The reason of such behaviour should be researched in the Qt quick render implementation. On certain platforms the render is not threaded by default. I guess you are working on Windows (see "Qt Quick" here). Hence, in a not threaded setting, by updating the variable and then sleeping, you are blocking the whole application, preventing the gui update.
You can use a QTimer to schedule the method execution at intervals, or set up a QML Timer for the exact same purpose.
Also, you don't need to save the new value in a temp variable (especially a var one which adds useless checks in this case). By setting an id inside the Text element you can directly set the text property. Here the revisited code:
import QtQuick 2.0;
Rectangle {
id: root
width: 200
height: 200
Text {
id: myText // the id!
anchors.fill: parent
text: "dummy" // dummy text || left empty || use "qmyTh.qm_no" (ensure a correct value is returned at creation time)
}
MouseArea {
anchors.fill: parent
onClicked: {
qmyTh.loop()
}
}
Connections {
target:qmyTh
onQm_noChanged: myText.text = qmyTh.qm_no // text directly set!
}
}
EDIT
It seems like the used render is threaded, hence my reasoning does not apply. There should be other problems. You can try to track down the problem exploiting the debugger and by adding console.info(...) statements in the JS handlers. Searching for the problem could be useful to track (possible) bugs in the libraries.
Depending on the background processing you have to run, I still think that using timers wouldn't be that bad. It really, truly depends on what you want to achieve. However, if you want to try threads, Qt documentation is full of explanations (as usual).
Have a look at this, this
and also absolutely this. Mind that a "moved" object (see the links) cannot be registered as a context property so you have to use one of the other ways to work with threads in a QML project.

Related

How to delete row from TableView properly

I'm using Qt 5.15.0 on Manjaro.
I have a table I created with TableView in qml.
I can delete a row with right click on the Id field like this:
The deletion etc works but I get these errors in qml:
The method for delete rows looks like this in main.qml:
function deleteRowFromDatabase(row) {
console.log("before" + model.countOfRows())
if (!model.removeEntry(row)) {
console.log(qsTr("remove row %1 failed").arg(row))
}
model = QuestionsProxyModel
console.log("after" + model.countOfRows())
}
The error point to the delegate row of id in main.qml
DelegateChoice {
column: 0
delegate: QuestionIdDelegate {
id: questionIdDelegate
width: tableView.columnWidthProvider(column)
text: model.id /// this is undefined
row: model.row
Component.onCompleted: {
questionIdDelegate.markForDelete.connect(
tableView.deleteRowFromDatabase)
}
}
}
Removing of the rows is implemented from C++ In a class derrived from QIdentityProxyModel in questionsproxmodel.h:
bool QuestionsProxyModel::removeEntry(int row)
{
return removeRows(row, 1);
}
This model takes a class QuestionSqlTableModel derrived from QSqlTableModel as a source model
The remove rows is implemented like this in questionssqltablemodel.qml:
bool QuestionSqlTableModel::removeRows(int row, int count,
const QModelIndex &parent)
{
auto result = QSqlTableModel::removeRows(row, count, parent);
if (result) {
select(); // row is not deleted from sql database until select is called
}
return result;
}
From my understanding the countOfRows of the model gets updated only after the select() is called so I assume between QSqlTableModel::removeRows and select the TableView reads one more time with the non existing row and causes these errors in QML. How can that be prevented?
Full Source code to try it out:
main.cpp:
#include <QDebug>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickStyle>
#include <QFile>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include "questionsproxymodel.h"
#include "questionsqltablemodel.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QUrl dbUrl{"file:///home/sandro/Desktop/test.db"};
auto exists = QFile::exists(dbUrl.toLocalFile());
auto db = QSqlDatabase::addDatabase("QSQLITE", "DBConnection");
db.setDatabaseName(dbUrl.toLocalFile());
db.open();
if (!exists) {
const QString questionTableName = "questions";
QSqlQuery query{db};
query.exec("CREATE TABLE " + questionTableName +
" ("
"id INTEGER PRIMARY KEY AUTOINCREMENT)");
}
QScopedPointer<QuestionSqlTableModel> questionSqlTableModel(
new QuestionSqlTableModel(nullptr, db));
QScopedPointer<QuestionsProxyModel> questionsProxyModel{
new QuestionsProxyModel};
questionsProxyModel->setSourceModel(questionSqlTableModel.get());
if (!exists) {
for (int i = 0; i < 10; ++i) {
questionsProxyModel->addNewEntry();
}
}
QQmlApplicationEngine engine;
qmlRegisterSingletonInstance<QuestionsProxyModel>(
"QuestionsProxyModels", 1, 0, "QuestionsProxyModel",
questionsProxyModel.get());
const QUrl url(QStringLiteral("qrc:/qml/main.qml"));
engine.load(url);
return app.exec();
}
questionssqltablemodel.h
#include <QSqlTableModel>
class QuestionSqlTableModel : public QSqlTableModel {
Q_OBJECT
public:
explicit QuestionSqlTableModel(QObject *parent = nullptr,
const QSqlDatabase &db = QSqlDatabase());
bool removeRows(int row, int count, const QModelIndex &parent) override;
};
questionssqltablemodel.cpp
#include "questionsqltablemodel.h"
#include <QBuffer>
#include <QDebug>
#include <QPixmap>
#include <QSqlError>
#include <QSqlField>
#include <QSqlRecord>
#include <QSqlRelationalDelegate>
QuestionSqlTableModel::QuestionSqlTableModel(QObject *parent,
const QSqlDatabase &db)
: QSqlTableModel{parent, db}
{
setTable("questions");
setSort(0, Qt::AscendingOrder);
if (!select()) {
qDebug() << "QuestionSqlTableModel: Select table questions failed";
}
setEditStrategy(EditStrategy::OnFieldChange);
}
bool QuestionSqlTableModel::removeRows(int row, int count,
const QModelIndex &parent)
{
auto result = QSqlTableModel::removeRows(row, count, parent);
if (result) {
select(); // row is not deleted from sql database until select is called
}
return result;
}
questionsproxymodel.h:
#include <QIdentityProxyModel>
#include <QObject>
class QuestionsProxyModel : public QIdentityProxyModel {
Q_OBJECT
enum questionRoles {
idRole = Qt::UserRole + 1,
};
public:
QuestionsProxyModel(QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE QVariant data(const QModelIndex &index,
int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;
bool addNewEntry();
Q_INVOKABLE bool removeEntry(int row);
Q_INVOKABLE int countOfRows() const;
private:
QModelIndex mapIndex(const QModelIndex &source, int role) const;
};
questionsproxymodel.h:
#include "questionsproxymodel.h"
#include <QBuffer>
#include <QDebug>
#include <QPixmap>
#include <QByteArray>
#include <QSqlError>
#include <QSqlTableModel>
QuestionsProxyModel::QuestionsProxyModel(QObject *parent)
: QIdentityProxyModel(parent)
{
}
QHash<int, QByteArray> QuestionsProxyModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[idRole] = "id";
return roles;
}
QVariant QuestionsProxyModel::data(const QModelIndex &index, int role) const
{
QModelIndex newIndex = mapIndex(index, role);
if (role == idRole) {
return QIdentityProxyModel::data(newIndex, Qt::DisplayRole);
}
return QIdentityProxyModel::data(newIndex, role);
}
bool QuestionsProxyModel::setData(const QModelIndex &index,
const QVariant &value, int role)
{
QModelIndex newIndex = mapIndex(index, role);
if (role == idRole) {
return QIdentityProxyModel::setData(newIndex, value, Qt::EditRole);
}
return QIdentityProxyModel::setData(newIndex, value, role);
}
bool QuestionsProxyModel::addNewEntry()
{
auto newRow = rowCount();
if (!insertRows(newRow, 1)) {
return false;
}
if (!setData(createIndex(newRow, 0), newRow + 1)) {
removeRows(newRow, 1);
return false;
}
auto sqlModel = qobject_cast<QSqlTableModel *>(sourceModel());
return sqlModel->submit();
}
bool QuestionsProxyModel::removeEntry(int row)
{
return removeRows(row, 1);
}
int QuestionsProxyModel::countOfRows() const
{
return rowCount();
}
QModelIndex QuestionsProxyModel::mapIndex(const QModelIndex &source,
int role) const
{
switch (role) {
case idRole:
return createIndex(source.row(), 0);
}
return source;
}
main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
import Qt.labs.qmlmodels 1.0
import QtQuick.Controls.Material 2.15
import QuestionsProxyModels 1.0
ApplicationWindow {
id: root
visible: true
width: 1460
height: 800
TableView {
id: tableView
width: parent.width
anchors.fill: parent
boundsBehavior: Flickable.StopAtBounds
reuseItems: true
clip: true
property var columnWidths: [60]
columnWidthProvider: function (column) {
return columnWidths[column]
}
model: QuestionsProxyModel
delegate: DelegateChooser {
id: chooser
DelegateChoice {
column: 0
delegate: QuestionIdDelegate {
id: questionIdDelegate
width: tableView.columnWidthProvider(column)
text: model.id
row: model.row
Component.onCompleted: {
questionIdDelegate.markForDelete.connect(
tableView.deleteRowFromDatabase)
}
}
}
}
ScrollBar.vertical: ScrollBar {}
function deleteRowFromDatabase(row) {
console.log("before" + model.countOfRows())
if (!model.removeEntry(row)) {
console.log(qsTr("remove row %1 failed").arg(row))
}
model = QuestionsProxyModel
console.log("after" + model.countOfRows())
}
}
}
QuestionIdDelegate.qml:
import QtQuick 2.15
import QtQuick.Controls 2.15
TextField {
property int row
signal markForDelete(int row)
id: root
implicitHeight: 100
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
readOnly: true
background: Frame {}
MouseArea {
id: mouseArea
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
eraseContextMenu.popup(root, 0, mouseArea.mouseY + 10)
}
}
Menu {
id: eraseContextMenu
y: root.y
MenuItem {
text: qsTr("Delete entry")
onTriggered: {
eraseDialog.open()
eraseContextMenu.close()
}
}
MenuItem {
text: qsTr("Cancel")
onTriggered: {
eraseContextMenu.close()
}
}
}
Dialog {
id: eraseDialog
title: qsTr("Delete database entry")
modal: true
focus: true
contentItem: Label {
id: label
text: qsTr("Do you really want to erase the entry with id %1?").arg(
root.text)
}
onAccepted: {
markForDelete(root.row)
}
standardButtons: Dialog.Ok | Dialog.Cancel
}
}
.pro:
QT += quick
QT += quickcontrols2
QT += sql
CONFIG += c++17
DEFINES += QT_DEPRECATED_WARNINGS
HEADERS += \
questionsproxymodel.h \
questionsqltablemodel.h
SOURCES += \
main.cpp \
questionsproxymodel.cpp \
questionsqltablemodel.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
edit:
The answer solves the error stated above. However I detected another problem with the code.
If I delete the row 1 and then the row 2 my output looks like this:
This binding loop error points Dialog in QuestionsIDDelegate:
Dialog {
id: eraseDialog
title: qsTr("Delete database entry")
modal: true
focus: true
contentItem: Label {
id: label
text: qsTr("Do you really want to erase the entry with id %1?").arg(
root.text)
}
onAccepted: {
markForDelete(root.row)
}
standardButtons: Dialog.Ok | Dialog.Cancel
}
One way to prevent this is to check that the property is not undefined:
text: model.id === undefined ? "" : model.id
I'm not going to fix the code, but I am going to try to explain why you are having trouble. I feel the why of things will help more people.
This image may not be 100% accurate but it is how one must visualize things in their mind. You have three distinct lanes an object lives in when you introduce QML: C++, QML Engine, and JavaScript engine. Each and every one of those lanes believes beyond a shadow of a doubt that they control the life and death of the object.
When you are just passing integers around that are passed by value, this is no issue. When you are passing QStrings around, because of the copy-on-write charter of Qt, this is only a minor issue. When you are passing real objects around, worse yet, complex containers of real objects, you have to understand this completely. The answer that "fixed" your problem really only masked it. You will find there is a lot of QML and JavaScript code out there that exists only to mask an application not respecting the lanes.
Had this application used an actual database there would be a fourth lane. Yes, SQLite provides an SQL interface and allows many things, but an actual database has an external engine providing shared access and controlling the life of cursors. SQLite files tend to be single user. Yes, multiple threads within your application can access it, but while your application is running you cannot open a terminal window and use command line tools to examine the database. Think of it more as a really nice indexed file system without sharing.
So, you create an object in C++ and then expose it to QML. The QML engine now believes beyond a doubt that it controls the life and death of that object despite not having its own copy.
QML is really feeble. It can't actually do much, so it has to hand any significant object off to JavaScript. The JavaScript engine now believes beyond a shadow of a doubt that it now controls the life and death of that object.
You need to also envision these lanes as independent threads. There will most likely be many threads within each, but, in general, any signal or communication between these will go on the event loop of the target as a queued event. That means it will only be processed when it finally bubbles to the top of the queue for that event loop.
This, btw, is why you can never use Smart Pointers when also using QML/JavaScript. Especially the kind that do reference counting and delete the object when there are no more references. There is no way for the C++ lane to know that QML or JavaScript are still using the object.
The answer telling you to check for undefined property is masking the problem that your code is drunk driving across all lanes. Eventually, on a faster (or sometimes slower) processor garbage collection for one of the lanes will run at a most inopportune moment and you will be greeted with a stack dump. (The drunk driving code will hit a tree that does not yield.)
Correct Solution #1:
Never use QML or JavaScript. Just use C++ and Widgets. Stay entirely within the C++ lane. If that is a route open to you it's a good way to go. There is an awful lot of production code out there doing just that. You can obtain a copy of this book (or just download the source code from the page) and muddle through building it.
Correct Solution #2:
Never actually do anything in QML or JavaScript. This is an all together different solution than #1. You can use QML for UI only, leaving all logic in C++. Your code is failing because you are trying to actually do something. I haven't built or tried your code. I simply saw
function deleteRowFromDatabase(row)
which shouldn't exist at all. C++ holds your model. You emit a signal from your QML/JavaScript lanes when user action requires deletion. This signal becomes a queued event in the C++ lane. When it is processed the row will be deleted and the model updated. If you have properly exposed your model to QML it will emit some form of "model changed" signal and the UI will update accordingly. One of the main points of MVC (Model-View-Controler) is communication to/from the model. When the data changes it notifies the view(s).
Correct Solution #3:
Never use C++. Have your C++ be a "Hello World!" shell that just launches your QML. Never create and share an object between C++ and the other two lanes. Do everything inside of JavaScript and QML.
Binding loop errors, in general, happen when code drunk drives across all three lanes. They also happen when code doesn't view each lane as at least one distinct thread with its own event loop. The C++ lane hasn't finished (perhaps not even started) the delete operation but your code is already trying to use the result.
I don't know why, but some people edit these things and delete the correct answers. The full information was too long for SO and is contained here,
https://www.logikalsolutions.com/wordpress/uncategorized/so-you-cant-get-your-qt-models-to-work-with-qml/
with source even.

How to get QML object's id property using C++ API

I need to parse a QML tree and get ids of all QML objects along the way which have it. I noticed that ids don't behave like normal properties (see the example below) – value returned from obj->property call is an invalid QVariant.
My question is – is there a way to retrieve object's id, even in some hacky (but reproductible) way?
Simplified example:
main.qml:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
Item {
id: howToGetThis
objectName: "item"
}
}
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QTimer>
#include <QDebug>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
QTimer::singleShot(1000, [&]() {
auto item = engine.rootObjects()[0]->findChild<QObject*>("item");
qDebug() << item->property("objectName");
qDebug() << item->property("id");
});
return app.exec();
}
Output:
QVariant(QString, "item")
QVariant(Invalid)
I think what you need is:
QString QQmlContext::nameForObject(QObject *object)
You can find the description here:
https://doc.qt.io/qt-5/qqmlcontext.html#nameForObject
Returns the name of object in this context, or an empty string if object is not named in the context. Objects are named by setContextProperty(), or by ids in the case of QML created contexts.
Based on comments received, a common pitfall is to call nameForObject using the wrong QQmlContext. (When that happens, you just the empty string.) To help with that, here is a more complete example:
QQuickItem* const focus_item = my_QQuickWindow->activeFocusItem();
if (!focus_item) {
fprintf(stderr, "no item has focus");
} else {
// There are many contexts in a hierarchy. You have to get the right one:
QQmlContext* const context = qmlContext(focus_item);
if (!context) {
// Unsure if this branch of code is even reachable:
fprintf(stderr, "item is not in any context?");
} else {
const QString focus_item_id = context->nameForObject(focus_item);
fprintf(stderr, "focus item: %s\n", focus_item_id.toStdString().c_str());
}
}

QML: Refresh issue while receivinig C++ signals passed from C++

Hi i have bee playing recently with Qt for mobile development. I started simple using all the code in C++ (given that I have more experience with it) and now I'm starting to use QML. In my app I have a QQuickWidget in the ui where I display a map. The goal is simple to center the map every time the phone emits the coordinates.
This is how I set up my QML
ui->quickWidget->setSource(QUrl("qrc:/geom_map.qml"));
and this is my QML file
Rectangle {
width: 300
height: 300
visible: true
Plugin {
id: osmPlugin
name: "esri"
// specify plugin parameters if necessary
// PluginParameter {
// name:
// value:
// }
}
Map {
id: map
anchors.fill: parent
plugin: osmPlugin
center: QtPositioning.coordinate(51.0, -114.0)
zoomLevel: 10
activeMapType: map.supportedMapTypes[1] // This selects the type of map to display
}
I declared a class to handle the sending of the coordinates to the QML and setup a connection on the QML for receiving the signal
#ifndef COORDUPDATE_H
#define COORDUPDATE_H
#include <QObject>
#include <QVariant>
class coordUpdate : public QObject
{
Q_OBJECT
public:
explicit coordUpdate(QObject *parent = nullptr);
// Q_INVOKABLE void sendupd(double xcoord, double ycoord);
private:
signals:
void sendCoord(QVariant xcoord,QVariant ycoord);
private slots:
public slots:
void sendupd(double xcoord, double ycoord);
};
#endif // COORDUPDATE_H
#include "coordupdate.h"
#include <QDebug>
coordUpdate::coordUpdate(QObject *parent) :
QObject(parent)
{
}
void coordUpdate::sendupd(double xcoord,double ycoord){
emit sendCoord(xcoord,ycoord);
qDebug() << "signal emitted";
}
The problem I'm having is that if I call
void MainWindow::setGPSLocation(QGeoPositionInfo geoPositionInfo)
{
statusBar()->showMessage("Updating position...",1000);
QString text="Location=unknown";
if (geoPositionInfo.isValid())
{
// get the current location coordinates
QGeoCoordinate geoCoordinate = geoPositionInfo.coordinate();
// transform coordinates to lat/lon
qreal latitude = geoCoordinate.latitude();
qreal longitude = geoCoordinate.longitude();
qreal altitude = geoCoordinate.altitude();
double lon = longitude;
double lat = latitude;
//on position updated we also need to record the data
coordUpdate upd;
ui->quickWidget->rootContext()->setContextProperty("updcoord",&upd);
ui->quickWidget->setSource(QUrl("qrc:/geom_map.qml"));
upd.sendupd(longitude,latitude);
qDebug() << "reading coordinates" << longitude << latitude;
}
}
Then the coordinates get updated but the whole thing gets refreshed. Is there a better way to update the information sent to the QML file? I know I would better developing in QML exclusively but I very interested in the use of QQuickWiget for the time being.
I'm not asking how to connect a signal from C++ to QML. The code does that. What I was asking was how to properly update the map with the information passed. I already read the documentation. Unfortunately not much of the documentation focus on QQuickWidgets but rather have a main class instantiated with QQmlEngine.
As described here http://doc.qt.io/qt-5/qtqml-cppintegration-overview.html, you have basically three ways to integrate C++ with QML code :
Expose your C++ class to QML ( properties, signals, slots..) and register it as an instanciable QML type
Same as 1 but register it as a singleton type so it is instantiated by the QML engine, not explicitly in the QML code
Same as 1 but make it available to QML via context properties for example, in this case, you create the instance of your class on the C++ side of your application
Solved it by moving the Connections inside the Map and redefining in the mainwindow.cpp and mainwindow.h the way the QML is instantiated. That way the map is refreshed properly every time the QML captures the signal.
Added to mainwindow.cpp
private:
coordUpdate *upd;
Changed
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->startU->setDisabled(true);
setupLogging();
setupGPS();
coordUpdate upd;
ui->quickWidget->rootContext()->setContextProperty("updcoord",upd);
ui->quickWidget->setSource(QUrl("qrc:/geom_map.qml"));
}
to
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->startU->setDisabled(true);
setupLogging();
setupGPS();
upd = new coordUpdate;
ui->quickWidget->rootContext()->setContextProperty("updcoord",upd);
ui->quickWidget->setSource(QUrl("qrc:/geom_map.qml"));
}
and in the QML
Rectangle {
width: 300
height: 300
visible: true
Plugin {
id: osmPlugin
name: "esri"
}
Map {
id: map
anchors.fill: parent
plugin: osmPlugin
// center: QtPositioning.coordinate(y, x)
zoomLevel: 15
activeMapType: map.supportedMapTypes[1] // This selects thetype of mapa to display
MapCircle {
id:circle
center: QtPositioning.coordinate(y, x)
radius: 50
color: 'red'
}
// Create connections with c++
Connections // Define actions for custom slots
{
id:cppConnection
target: updcoord
onSendCoord: {
// To access signal parameter, name the parameter
console.log("signal received",ycoord,xcoord)
circle.center = QtPositioning.coordinate(ycoord,xcoord)
}
}
}
}

C++ / QML interactive combo box implementation

I have two combo boxes, the data for the second one being determined by that of the first one. The number of strings in the second combo box varies from 2 to 4. If I:
select a new string in the first combo box and
the last choice is selected
in the second combo box with a longer list than the previous list of
that box,
the currentString in the second combo box remains and overrides the correct text
For instance, if I select Scubapro in the first combo box (4 options in 2nd box) and Smart in the second combo box (the 4th option), then select any other choice in the first combo box again (< 4 options in 2nd box), the entry in the second combo box remains "Smart", which is inappropriate. The correct list is, however, loaded into the 2nd combo box. Inspection of the underlying stringlist also suggests that it contains the correct data. The problem appears to be the visual updating of the second combo box. The heart of the algorithm comes from Stackoverflow and is the generator called each time text in combo box 1 changes.
What can one do to rectify this?
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include <QQuickView>
#include <QQuickItem>
#include <QStringListModel>
#include <QQmlContext>
#include <QDebug>
QStringList dat1, dat2, dat3;
QStringList vendorList;
class Generator : public QObject
Q_OBJECT
QStringListModel * m_model;
public:
Generator(QStringListModel * model) : m_model(model) {}
Q_INVOKABLE void generate(const QVariant & val) {
m_model->removeRows(0,m_model->rowCount()); // This has no effect
if (QString::compare(val.toString(), QString::fromStdString("Mares"), Qt::CaseInsensitive) == 0) {
m_model->setStringList(dat1);
}
else {
if (QString::compare(val.toString(), QString::fromStdString("ScubaPro"), Qt::CaseInsensitive) == 0) {
m_model->setStringList(dat2);
}
else
m_model->setStringList(dat3);
}
};
int main(int argc, char *argv[])
{
QStringListModel model1, model2;
generator(&model2);
dat1 << "Puck" << "Nemo" << "Matrix";
dat2 << "Aladin" << "Meridian" << "Galilio" << "Smart";
dat3 << "D4" << "D6";
vendorList << "Mares" << "Scubapro" << "Suunto" << "Shearwater";
model1.setStringList(vendorList);
QGuiApplication app(argc, argv);
QQuickView view;
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("model1", &model1);
ctxt->setContextProperty("model2", &model2);
ctxt->setContextProperty("generator", &generator);
view.setSource(QUrl("qrc:main.qml"));
view.show();
return app.exec();
}
#include "main.moc"
Here is the QML:
import QtQuick 2.0
import QtQuick.Controls 1.0
Rectangle {
width: 400; height: 300
Text { text: "Vendor"; }
Text {
x: 200
text: "Product"; }
ComboBox {
id: box2
objectName: "productBox"
x:200; y:25; width: 180
model: model2
textRole: "display"
}
ComboBox {
y:25; width: 180
id: box1
model: model1
textRole: "display"
onCurrentTextChanged: {
generator.generate(currentText)
}
}
}
Any comments are highly appreciated.
The ComboBox item does not react to changes performed under the hood to the model.
There are a couple of solutions to work around it.
A possible one is to reassign the model to itself at the end of the signal handler, by using the statement:
model = model;
As from the documentation:
Changing the model after initialization will reset currentIndex to 0.
Otherwise, you can explicitly set currentIndex to your preferred value or, even better, to -1.
In fact, from the documentation we have that:
Setting currentIndex to -1 will reset the selection and clear the text label.

Qt 5.2 QObject::connect: Cannot connect (null)::

I'm a noob - not good with QML or C++ yet, but getting there. I seem to have hit a stumbling block I can't get past. I'm receiving the following error in my attempt to run a build and I'm not sure what I missed in my code...
**QObject::connect: Cannot connect (null)::buttonClicked_enable() to TLA_Funcs::sys_enable()
**
I've looked through the other versions of the question here and it seems that I have my code correct, but I still get the error. Can someone take a peek? Here's the relevant sample of my code (the rest is too long and I left out the guts of the function - too long).
QML Code:
Rectangle {
id: main
width: 800
height: 600
color: "#abcfe9"
border.width: 4
border.color: "#000000"
signal buttonClicked_enable()
Button {
id: enable
x: 628
y: 55
text: "ENABLE"
onClicked:buttonClicked_enable()
}
//....
}
My class header:
#ifndef TLA_FUNCS_H
#define TLA_FUNCS_H
#include <QObject>
class TLA_Funcs : public QObject
{
Q_OBJECT
public:
explicit TLA_Funcs(QObject *parent = 0);
signals:
public slots:
Q_INVOKABLE void sys_enable(){return ;}
private:
};
#endif
And in my main.cpp file:
#include "TLA_Funcs.h"
TLA_Funcs::TLA_Funcs(QObject *parent) :
QObject(parent)
{
}
int main (int argc, char*argv[]) {
QGuiApplication app(argc, argv);
QQuickView *view = new QQuickView(QUrl("main.qml"));
view->show();
QQuickItem *item = view->rootObject();
TLA_Funcs *funcs = new TLA_Funcs();
QObject::connect(item, SIGNAL(buttonClicked_enable()), funcs, SLOT(sys_enable()));
}
I've defined the signals in the parent rectangle, and in the button code I've tried using:
onClicked:main.buttonClicked_enable()
as well as:
onClicked: {
buttonClicked_enable()
TLA_Funcs.sys_enable()
}
That didn't help either. I also tried defining the functions under "signal" in the class, but that made more of a mess. Can someone at least point me in the right direction, and keep in mind, I'm still a noob... Thanks All!!!
Problem Solved: there was an error in the .pro file. I copied the code into a new project and it worked correctly 100%. Thanks all for the help!