Unsolved How to make updating of two or more bindings atomic
-
Hi all,
how can I make two or more bindings update in a atomic manner when their dependent property change? For example I have the following code:
property int a: foo[i].a property int b: foo[i].b
Now if other properties depent on a or b I want them to not update before a AND b are updated due to a change of i.
Thanks!
-
@DuBu I don't see a way to do that without resorting to imperative code. Maybe the easiest solution is to remove a, b etc. bindings and do instead
signal abChanged(int a, int b) // or property ab onIChanged: { a = foo[i].a b = foo[i].b abChanged(a, b) // or ab = ... }
-
@Eeli-K Too bad. I think to use imperative code instead is not always an option. The opposite way is even worse:
property int i: foo[a][b]
Property "i" depends on a AND b. That way "i" gets calculated twice. (That's just a simple example.)
A more complex one:onAChanged: calculateComplexGraphic(a, b) onBChanged: calculateComplexGraphic(a, b)
Now the calculation gets started twice and the first one is done with a possibly wrong value of a or b.
I think the whole topic is worth a feature request. -
Hi,
How can you guarantee that you'll change these values atomically on your side ?
In any case, if you are dependant on several values like that, it might make more sense to encapsulate them in a small strucuture and send that over. That will also make things clearer as you would have only one property to update while you can still provide the single values if you need them like for example size with width and hight.
-
@SGaist I can't guarantee the values get changed atomically on my side. At initialization time they are and at runtime they aren't. For example you have a component with min, max, value. At runtime only value is likely to change. But the initialization takes much longer.
What do you mean with "small structure"? A js object like ab = { a, b }? That would make changing only one property harder. Or, as you proposed, having both options make everything more complex. Or did I get you wrong? -
Why would it be harder ? You are just sending two values, one of which may or may not have changed.
Can you give a more precise description of your system ? Because currently it seems that you want to synchronise something that is not synchronised at all from the beginning.
-
@SGaist Yes, the values are not synchronized. But that's what I want to achieve, when a value "X" depends on two or more other unsynchronized values how can I avoid to recalculate value "X" each time a value changes. I'm looking for a mechanism to kind of "synchronize" them (Like with imperative code @Eeli-K already proposed.) or as a start a way of ordering their updates (I.e. with foo[a][b] a new "b" doesn't really makes sense with an old "a").
-
To me this seems like the right time to include a cpp-extension to your QML-Project
#ifndef ATOMICPROPERTY_H #define ATOMICPROPERTY_H #include <QObject> class atomicProperty :public QObject { Q_OBJECT Q_PROPERTY(int A READ getA WRITE setA NOTIFY AChanged) Q_PROPERTY(int B READ getB WRITE setB NOTIFY BChanged) Q_PROPERTY(int D READ getD NOTIFY DChanged) public: atomicProperty(QObject * parent = Q_NULLPTR): QObject(parent){ QObject::connect(this, &atomicProperty::AChanged, this, &atomicProperty::checkD); QObject::connect(this, &atomicProperty::BChanged, this, &atomicProperty::checkD); } public slots: Q_INVOKABLE int getA(){return A;} Q_INVOKABLE int getB(){return B;} Q_INVOKABLE int getD(){return D;} Q_INVOKABLE void setA(int value){ if(value != A){ A = value; m_aChanged = true; emit AChanged(); } } Q_INVOKABLE void setB(int value){ if(value != B){ B = value; m_bChanged = true; emit BChanged(); } } signals: void AChanged(); void BChanged(); void DChanged(); private slots: void checkD(){ if(m_aChanged && m_bChanged){ m_aChanged = false; m_bChanged = false; D = A+B; emit DChanged(); } } private: int A = 0; int B = 0; int D = 0; bool m_aChanged = false; bool m_bChanged = false; }; #endif // ATOMICPROPERTY_H
expose it to your QML object:
qmlRegisterTyp<"atomicProperty",1,0, "atomicProperty");
and import it.
Than you have the binding you want.
-
@DuBu The declarative nature of QML and the engine implementation means that you can't rely on the order of execution of certain things. It's just impossible to do what you want and at the same time avoid imperative code. You have to do it either in javascript or in C++. Of course you can encapsulate and hide it somewhere so that you don't need to see it all the time, but the code must be somewhere. I think it would be impossible to change the QML language and engine to do that; it wouldn't be simple and efficient anymore.
You have to know two things: 1: The triggering event which triggers property updates and 2: The end of the property updates. In declarative QML code you can know only number 1 but even that without knowing whether other updates will be done or have already been done.
The big question in my opinion is "how do you know that all of the properties which are about to change have changed?" Even if you do know the order they are changed the xChanged signal of a property (in pure QML) is sent only if the value has actually changed. So, what happens if A is changed but B is not? Or A is not changed but B is? It's impossible to know without sending xChanged signals even when the properties haven't changed. And if you do that you still have to keep some kind of counter to see when all of the dependencies have changed. You just have to use imperative code.
Just give up the idea of a neat declarative QML-only solution and decide how and where you put the imperative code.
-
Ok, thanks guys, as you showed there's no implicit way of doing what I want yet. I'll do it explicit (i.e. with imperative code) or try to avoid such scenarios at all.
BTW: I wrote a post about an issue in ListView which may be caused by the order of updates:
When you want to have 4 items of an ListView filling the whole height of the view you could write the height of a delegate item like:height: ListView.view.height / 4
Now if you put 10000 items in the ListView it creates/binds/draws all 10000 in order to remove 9996 again when the height of the ListView gets initialized. If the height of the ListView would be initialized before the delegates, only 4 elements would be created.
I would vote for a syntax enhancement to mark a binding as to be updated before other bindings.
-
@DuBu Delegates are created on-the-fly, there will be maybe six extra objects beyond the visible ones.
-
@Eeli-K Yes, but it seems if the height of the delegate is 0, then all items are visible and therefore created. I workaround that by writing:
height: (ListView.view.height || 1) / 4
-
There's another issue we noticed related to that topic:
We have a couple of nested Grid-/Column-/RowLayouts together with Items which have margins. At the end of that hierarchy there's a Canvas components which draws arcs. Sometimes (!) during startup/inititalization and between two updates of bindings one of the layout components has a width of 0. If that happens the arc function of the canvas complains about an invalid radius being negative, which results from the 0 width and the margins. After initialization everything works fine and there's no item with a width of 0 anymore. It's just a matter of the order the layouts calculate it's sizes. It also shows that the arcs get redrawn a couple of times during initialization.