Q_PROPERTY NOTIFY during initialization race condition?
-
I have a QML script that displays periodic data from a C++ object using a Q_PROPERTY -- something very basic like:
@
class MyCppClass : public QObject
{
Q_OBJECT
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)public:
MyCppClass() : m_iValue(DEFAULT_VALUE);int value() { return m_iValue; } void setValue(int i) { if (i == m_iValue) return; m_iValue = i; emit valueChanged(i); }
signals:
void valueChanged(int);private:
int m_iValue;
};
@When the QML app starts it requests the data values from a database and calls setValue to update the display.
I am seeing, very rarely, that the display value does not update properly. I can see through debugging statements that setValue() is called properly and the
emit valueChanged()
occurs but the display shows the default value.It seems as if the
emit valueChanged
is occurring before the entire script is finished initializing. It knows the notify occurred, but it is using the original value. If setValue() is called a second time, with a different value, the display updates fine.Anyone have any ideas of what could be going on? This is with QT 4.8.4.
-
Tried adding a mutex to the value getter and setter but that did not help. I can see the normal pattern is:
@
valueGet()
valueSet() changed
valueSet() emit
======================== Component.onCompleted
valueGet()
@but when it fails, Qt does not make another get after the changed signal:
@
valueGet()
valueSet() changed
valueSet() emit
======================== Component.onCompleted
@Still no ideas of what's going on.
-
I am seeing the same problem. The signal that the value changed is lost if it occurs before the component is complete. Is there any non-kludgy fix for this?
-
Can't you just wait until the whole app is loaded before you try and change any properties? That is what the Component.onCompleted signal is for usually.
-
I am fetching the values from a REST API when the property is created in QML. Sometimes the value will ready when the getter is called. Some of the requests are slower and might show up latter when the notify signal is emitted. I'm fine with that.
The only hack I can find is to force all the properties to emit a notify signal in the Component.onCompleted signal. For pages with lots of properties it's pretty wasteful.
-
Usually I would say you fetch the remote content and set it to a model or something? And not directly to the properties, at least that is how I do it. So if the model is changed all properties get updated by the property binding in QML and it work great this way.
maybe you can show a snipped from your QML code you fetch the content and set the values?small example how I do it
@
Item {
id: rootItem
ListView {
id: listView
delegate: Text { text: modelData.text }
}
RemoteAPI {
id: remoteAPI
onRequestFinished: listView.model = data // data comes from c++ signal
}
Component.onCompleted: remoteAPI.requestData() // c++ slot
}
@ -
The QML looks something like this:
@
ListView {
model: ListModel {
ListElement { key: "/some/rest/uri" };
...
}delegate: Text { property RestProperty prop : restProxy.PropertyFactory(key); text: prop.key + " = " + prop.value; }
}
@The RestProperty is a C++ class like the OP originally showed. The
restProxy has a factory method which creates or reuses an existing
property. Since the times vary to fetch the properties I fill them in as the come instead of waiting for all of them to return.In my app, the ListView is actually a Menu object that can be used to modify any of the properties.
-
The QML looks something like this:
@
ListView {
model: ListModel {
ListElement { key: "/some/rest/uri" };
...
}delegate: Text { property RestProperty prop : restProxy.PropertyFactory(key); text: prop.key + " = " + prop.value; }
}
@The RestProperty is a C++ class like the OP originally showed. The restProxy has a factory method which creates or reuses an existing property. Since the times vary to fetch the properties I fill them in as the come instead of waiting for all of them to return.
In my app, the ListView is actually a Menu object that can be used to modify any of the properties.
-
I boiled it down to a simple example.
The first read forces a notify which should tell QML to refetch the value:
@
class MyObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QString value READ valueGet WRITE valueSet NOTIFY valueChanged )public:
MyObject(QObject *parent=0) : QObject(parent), m_value("first") { } MyObject(const QString &value, QObject *parent=0) : QObject(parent), m_value(value) { } QString valueGet() { QString value = m_value; // simulate a property change after the initial read static int once = 1; if (once) { once = 0; valueSet("second"); } return value; } void valueSet(const QString &value) { if (value != m_value) { m_value = value; emit valueChanged(value); } } QString m_value;
signals:
void valueChanged(QString);
};
@And QML:
@
property int counter : 0;Timer
{
interval: 5000;
running: true;
onTriggered: { counter++; }
}Item
{
MyObject {id: foo;}Text { x: 20; y: 20; color: "white"; text : foo.value + " " + counter; }
}
@I'd expect to see "second" possibly after a short flash of "first". The timer is needed to refresh the actual value. As far as I can tell this is a bug.
-
weird, I've tested this myself now and it just works as expected on my system!?
my complete code follows.my custom c++ object (cppobject.h):
@
#ifndef CPPOBJECT_H
#define CPPOBJECT_H#include <QObject>
class CppObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QString value READ value WRITE setValue NOTIFY valueChanged)public:
explicit CppObject(QObject *parent = 0) :
QObject(parent), m_value("foo bar") {}QString value() const { return m_value; }
signals:
void valueChanged(QString arg);public slots:
void setValue(QString arg) {
if (m_value != arg) {
m_value = arg;
emit valueChanged(arg);
}
}private:
QString m_value;
};#endif // CPPOBJECT_H
@main.cpp
@
#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"#include <QtQml>
#include "cppobject.h"int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);qmlRegisterType<CppObject>("CppObject", 1, 0, "CppObject"); QtQuick2ApplicationViewer viewer; viewer.setMainQmlFile(QStringLiteral("qml/quicktest/main.qml")); viewer.showExpanded(); return app.exec();
}
@main.qml
@
import QtQuick 2.2
import CppObject 1.0Rectangle {
width: 300
height: 300property int counter: 0 Text { id: textItem anchors.centerIn: parent text: cppObject.value + " " + counter } CppObject { id: cppObject } Timer { interval: 1000 onTriggered: counter++ running: true repeat: true }
}
@just added the timer for testing, it works without the timer and shows the initial value without any emit valueChanged!?
-
The initial value shows fine for me too. The problem is if the value changes after the initial get but sometime before the QML finishes initialization. That's what I was trying to simulate by making the get modify the value.
-
yeah I don't think it's a good idea to invoke the setter from the getter, that is just bad design even if this is just an example that can only lead to errors. :D
still an very weird problem, I don't know how to replicate that "error" properly, with my QML apps I just wait until the QML view is ready before I do anything, I don't know how large your app is but usually that is very fast even on mobile devices.anyway that doesn't seem like a bug to me, if you emit a signal before the receiver slot is ready and connected, of course it won't work.
If you really have this special case you might want to delay t he property binding until teh QMl component is ready, you can try it like this I guess:
@
Component.onCompleted: yourProperty = Qt.binding(cppObject.value)
@ -
This is frustrating because this is a significant problem for me.
In the cases were this fails I can see that QML has already called the property accessor so QML should have connect all signals and slots so I don't feel I'm emitting a signal at an inappropriate time.