QQmlDelegateModel: De-bounce initialization of two-way bindings

When installing the reverse binding on a QQmlDelegateModelItem, the
binding is evaluated right away and tries to write the model data. We
don't want that since we know that the model data already contains the
same value.

Use a temporary filtering metaobject to block writes to this property
while installing the reverse binding. This causes the bounced write to
fail.

Amends commit 4bd5b31279

Pick-to: 6.10
Task-number: QTBUG-132420
Change-Id: Ib001f669046635cc3ae830154f22d95ee1d3924c
Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
This commit is contained in:
Ulf Hermann 2025-09-01 16:51:12 +02:00
parent 7873f3484c
commit c5832c09c6
2 changed files with 14 additions and 12 deletions

View File

@ -953,7 +953,8 @@ public:
, access(access) , access(access)
{} {}
void operator()(const QMetaObject *modelMetaObject, QObject *modelObject) const template<typename ModelObjectType>
void operator()(const QMetaObject *modelMetaObject, ModelObjectType *modelObject) const
{ {
const int end = modelMetaObject->propertyCount() + modelMetaObject->propertyOffset(); const int end = modelMetaObject->propertyCount() + modelMetaObject->propertyOffset();
for (int i = modelMetaObject->propertyOffset(); i < end; ++i) { for (int i = modelMetaObject->propertyOffset(); i < end; ++i) {
@ -982,7 +983,18 @@ public:
QQmlAnyBinding reverse = QQmlPropertyToPropertyBinding::create( QQmlAnyBinding reverse = QQmlPropertyToPropertyBinding::create(
engine, targetProp, sourceProp); engine, targetProp, sourceProp);
reverse.setSticky(); reverse.setSticky();
if constexpr (std::is_base_of_v<QQmlDelegateModelItem, ModelObjectType>) {
// Temporarily take away the metaobject so that the property can't actually be
// written. It shouldn't be written since we've just synchronized it the other way
// when installing the "forward" binding.
QQmlDelegateModelReadOnlyMetaObject readonly(modelObject, i);
reverse.installOn(sourceProp); reverse.installOn(sourceProp);
} else {
// It's only not a QQmlDelegateModelItem if the model is actually an ObjectModel.
// In that case, we hope that the generic equality check when setting a property
// is enough to de-bounce it.
reverse.installOn(sourceProp);
}
} }
} }

View File

@ -344,7 +344,6 @@ void tst_QQmlRangeModel::intRange()
QCOMPARE(currentItem->property("currentValue"), oldValue); QCOMPARE(currentItem->property("currentValue"), oldValue);
// nothing happened so far, so there shouldn't have been any calls to setData // nothing happened so far, so there shouldn't have been any calls to setData
QEXPECT_FAIL("ReadWrite", "Unexpected call to setData", Continue);
QCOMPARE(model.setDataCalls, QList<int>{}); QCOMPARE(model.setDataCalls, QList<int>{});
model.setDataCalls.clear(); model.setDataCalls.clear();
model.dataCalls.clear(); model.dataCalls.clear();
@ -383,14 +382,6 @@ void tst_QQmlRangeModel::objectRange()
std::vector<Entry *> objects{entry.get()}; std::vector<Entry *> objects{entry.get()};
RangeModel model(&objects); RangeModel model(&objects);
#ifndef QT_NO_DEBUG
// with ReadWrite, spurious call to setData(RangeModelDataRole) during loading
if (writeBack) {
QTest::ignoreMessage(QtCriticalMsg,
QRegularExpression("Not able to assign QVariant\\(.*\\) to Entry*"));
}
#endif
auto view = makeView({ auto view = makeView({
{"delegateModelAccess", delegateModelAccess}, {"delegateModelAccess", delegateModelAccess},
{"model", QVariant::fromValue(&model)} {"model", QVariant::fromValue(&model)}
@ -405,7 +396,6 @@ void tst_QQmlRangeModel::objectRange()
QVERIFY(model.dataCalls.contains(Qt::RangeModelDataRole)); QVERIFY(model.dataCalls.contains(Qt::RangeModelDataRole));
model.dataCalls.clear(); model.dataCalls.clear();
// there shouldn't have been any attempts to write yet // there shouldn't have been any attempts to write yet
QEXPECT_FAIL("ReadWrite", "Premature calls to setData()", Continue);
QCOMPARE(model.setDataCalls, QList<int>{}); QCOMPARE(model.setDataCalls, QList<int>{});
model.setDataCalls.clear(); model.setDataCalls.clear();