I'm a beginner of QML, I'm trying to follow QtQuick's standard components' behavior as a guideline.
I wanted to implement a user input component in the way TextEdit works. But now it really confuse me how the text
property is implemented.
The TextEdit's text
is write-only, and always display what, if it exists, its text
property is assigned/bind to .
Operation
run QML
Window {
visible: true
property string foo: "foo"
TextEdit { text: foo }
}
Type whatever in the TextEdit
Expected The text always display "foo" and cannot be edited.
Actual The text changed with what you typed.
Conclusion Hypothesis 1 is false.
The text
property is set to what it's bind to on initialization.
And once the user edit it, the text
is assigned to a new value, which breaks the binding.
Operation
run QML
Window {
visible: true
property string foo: "foo"
TextEdit { id: t1; text: foo; x: 0; }
TextEdit { id: t2; text: t1.text; x: 100 }
}
type something in t2, and then type something in t1
Expected After t2 edited, editing t1 will no longer change t2's display.
Actual t2 can be edited solely. But once t1 is edited again, t2's display is bind to t1 again.
Conclusion Hypothesis 2 is false.
The TextEdit has something like internalText
which represent what the user input. And the TextEdit display either internalText
or text
whichever changed latest.
Operation
run QML
Window {
visible: true
property string foo: "foo"
TextEdit { id: t1; text: foo; x: 0; }
Button { x: 100; onClick: foo = "bar" } // A custom button
}
type "blablabla" in t1
click the button
type "blablabla" in t1
click the button, click the button, click the button a thousand times
Expected t1 can be edited, and on button clicked the display is reset to "bar".
Actual The display is set to "bar" on the first click, and after that clicking the button makes no effect at all.
Conclusion Hypothesis 3 is false ... What the ...??
After reading the QML Reference several times several times especially the Property Binding part I don't know how such property which can be both written and read (also not like 2-way binding of Angular). I'm puzzled.
And after doing experiment 3 I'm really confused, can someone explain how it works?
Bindings are more like a meta-property than an actual value. It's easy to understand once you imagine how it works from the c++ side.
Bindings are realised using signals and slots. The simple QML line
text: foo.text
as example would be equivalent to the following c++ code (simplified):
// first: call the getter of text on foo, and the setter on this
this->setText(foo->text());
// second: connect the change signal of foo to this
connect(foo, SIGNAL(textChanged(QString)),
this, SLOT(setText(QString)));
As you can see, a binding is basically: "Assign me the current value, and whenever your value changes, update my value to yours". But since "this" still has it's own copy of the text, you can of couse modify it by calling this->setText("something")
without breaking the binding (or in c++ terms - the signal connection)
This also explains the 2nd (and 1st) experiment: Whenever you change the text of t2 in the GUI, the internal text of t2 gets updated. However, since the binding still exists, everytime you change t1, t1 emits the textChanged
signal and thus updates t2 as well.
Now for the 3rd experiment, it gets a little tricky. The c++ code is heavily simplified and only explains what happens, not how it actually works. For the 3rd case, we have to look at the javascript side of qml. More specific: How bindings are created from javascript:
// the qml line
text: foo
// is eqivalent to the js
t1.text = Qt.binding(function(){return foo;})
So you don't really assign a value to t1.text
, but a "binding object". (The Qt.binding
function does some magic and returns such an object) This internally does the same as the c++ code above, but with one difference. Once you assign a new value to t1.text
, like you did in your button, the old binding gets deleted, together with the signal connection, as you replace the binding object by a new value.
So in your JS code in the button, you replace the binding object with the value "bar", thus destroying it. Editing the text from the GUI does not destroy the binding, as only the internal value changes, and nothing is actually assigned to the property from QML.
EDIT: Now the reason why subsequent button clicks don't do anything is: After pressing the button once, foo is set to "bar", which triggers the textChanged
signal and thus changes t1. However, the next time you press the button and foo is set to bar again, it does nothing, because it is already "bar". You don't change anything by setting it to the same value it is right now. Imagine the setter implementation like this:
void setText(QString text) {
if(this->text == text)
return;
this->text = text;
emit textChanged(text);
}
If you modify your code, for example like this:
Button { x: 100; onClick: foo = t1.text + "bar" }
It will always work and update foo accordingly, as this expression always generates a new value for foo.
I hope I could explain the behaviour understandable to you.