To Bind or Not to Bind with Model Data. Am I Overcomplicating This?
-
I have a simple model and within it a simple text property "name". I would like to bind this to a QML TextField:
// C++, Within the model with instance myModel Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged) // QML TextField { text = myModel.name }
This is fine for reading, but when the user goes to edit the text field this does not change the model. In other words, WRITE is not called as one would expect. My first and biggest question is why on earth not? Isn't this the point of a binding? Why even have a WRITE attribute if not.
The first thing you naturally try is to manually set the text:
// C++, Within the model with instance myModel Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged) // QML (Error Binding Loop) TextField { text = myModel.name onDisplayTextChanged: myModel.name = text }
Which results in a binding loop warning. So reading around online, I find that people suggest that instead of binding you have specific sets for specific events. A common one is on object completion:
// C++, Within the model with instance myModel Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged) // QML TextField { id: textFieldDescription Component.onCompleted: text = myModel.name onDisplayTextChanged: myModel.name = text }
This is fine for simpler model/view relationships (though my original question remains; this seems unnecessary). But what if you have other events that can change name outside of what you see here? You have limited yourself by breaking the binding. So the suggestion I typically see is to do this:
// C++, Within the model with instance myModel Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged) // QML MyModel { id: myModel onNameChanged: textFieldName.text = name } TextField { id: textFieldName onDisplayTextChanged: myModel.name = text }
So question two (which is technically 3 questions) Am I overcomplicating this? Is this really how we are supposed to do it? There isn't a way to just use a binding that says "user edits = writes with no binding loop"? It seems overcomplicated to me.
Further, what if we want to do something more complicated with delegates?
// C++: Within a NameCheck class which holds only these two members Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged) Q_PROPERTY(bool checkStatus READ getCheckStatus WRITE setCheckStatus NOTIFY checkStatusChanged) // C++: Within the model class with instance myModel Q_PROPERTY(QList<NameCheck*> allPeople READ getAllPeople WRITE setAllPeople NOTIFY allPeopleChanged) // QML: MyModel { id: myModel // I want something like: onCheckStatusChanged // set check status of checkDelegate index n. } ListView { id: listView model: myModel.allPeople delegate: CheckDelegate { id: checkDelegate text: modelData.name onCheckStateChanged: modelData.checkStatus = checked } }
So this gets pretty weird, when I would really just like to have a simple binding. Final question is how do I pull off this checkdelegate situation in a clean elegant way?
Thanks for reading.
-
So once again, I'm going to answer my own question. What is it about formalizing your problem that helps you solve it? The simple elegant way to accomplish this is below:
// C++, Within the model with instance myModel Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged) void MyModel::setName(QString name) { if (name != m_name) { m_name = std::move(name); emit nameChanged(); } } // QML TextField { text = myModel.name onDisplayTextChanged: myModel.name = text }
The key to this is protecting the call to
emit
with the if check. When a user changes the text theWRITE
method is called, m_name is changed, and theNOTIFY
emit fires which causes the binding to callWRITE
once again, but now name and m_name are equal soemit
is not called and the loop stops here. You get all the benefits of having your binding still withtext = myModel.name
but you can also manually set it.I really think this information needs to be in the documentation somewhere. It's such an incredibly common problem, and I searched for a while without finding this.
-
In Qt Creator, create your Q_PROPERTY statement first. Then right click and choose Refactor->Generate missing Q_PROPERTY members. This will create the default function members that do what you learned.
Another option is MEMBER:
Q_PROPERTY(type name MEMBER memberName)
This creates functions for reading and writing the property. They are opaque to you so may not be what you want. I cannot remember if MEMBER requires a NOTIFY or not. I think it does not.