Properly wire up DelegateModel's modelChanged signal

If the model contents change we need to notify. This enables the signal
propagation for Instantiator, Repeater, ListView, and GridView.

Pick-to: 6.10
Task-number: QTBUG-139941
Change-Id: I384dcd296068ca7abfd1cad9fe662ae6e8938338
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Ulf Hermann 2025-09-10 12:13:12 +02:00
parent 8ae3765c3f
commit 6803e9c908
9 changed files with 94 additions and 9 deletions

View File

@ -388,6 +388,9 @@ void QQmlDelegateModel::setModel(const QVariant &model)
{
Q_D(QQmlDelegateModel);
if (d->m_adaptorModel.model() == model)
return;
if (d->m_complete)
_q_itemsRemoved(0, d->m_count);
@ -420,6 +423,8 @@ void QQmlDelegateModel::setModel(const QVariant &model)
if (aimPrivate->resetting)
QObject::connect(aim, &QAbstractItemModel::modelReset, this, &QQmlDelegateModel::handleModelReset, Qt::SingleShotConnection);
}
emit modelChanged();
}
/*!
@ -2006,8 +2011,10 @@ void QQmlDelegateModel::_q_modelAboutToBeReset()
// to throw away all the setup that we did
handleModelReset();
} else {
// If they did change, we give up and just start from scratch via setMode
setModel(QVariant::fromValue(model()));
// If they did change, we give up and just start from scratch via setModel
QVariant m = model();
setModel(QVariant());
setModel(m);
// but we still have to call handleModelReset, otherwise views will
// not refresh
handleModelReset();

View File

@ -39,7 +39,7 @@ class Q_QMLMODELS_EXPORT QQmlDelegateModel : public QQmlInstanceModel, public QQ
Q_OBJECT
Q_DECLARE_PRIVATE(QQmlDelegateModel)
Q_PROPERTY(QVariant model READ model WRITE setModel)
Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged)
Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
Q_PROPERTY(QString filterOnGroup READ filterGroup WRITE setFilterGroup NOTIFY filterGroupChanged RESET resetFilterGroup)
Q_PROPERTY(QQmlDelegateModelGroup *items READ items CONSTANT) //TODO : worth renaming?
@ -154,6 +154,7 @@ Q_SIGNALS:
void rootIndexChanged();
void delegateChanged();
Q_REVISION(6, 10) void delegateModelAccessChanged();
Q_REVISION(6, 10) void modelChanged();
private Q_SLOTS:
void _q_itemsChanged(int index, int count, const QVector<int> &roles);

View File

@ -99,6 +99,8 @@ int VDMListDelegateDataType::metaCall(
accessor->cachedDataClean = false;
} else {
model->list.set(accessor->index, data);
if (QQmlDelegateModel *delegateModel = accessor->metaType->model)
emit delegateModel->modelChanged();
}
QMetaObject::activate(accessor, this, id - propertyOffset, nullptr);
emit accessor->modelDataChanged();

View File

@ -53,6 +53,10 @@ public:
this, &QQmlInstantiatorPrivate::_q_modelUpdated);
QObjectPrivate::connect(instanceModel, &QQmlInstanceModel::createdItem,
this, &QQmlInstantiatorPrivate::_q_createdItem);
if (ownModel) {
QObject::connect(model->delegateModel(), &QQmlDelegateModel::modelChanged,
q, &QQmlInstantiator::modelChanged);
}
regenerate();
}
@ -67,6 +71,10 @@ public:
this, &QQmlInstantiatorPrivate::_q_modelUpdated);
QObjectPrivate::disconnect(instanceModel, &QQmlInstanceModel::createdItem,
this, &QQmlInstantiatorPrivate::_q_createdItem);
if (ownModel) {
QObject::disconnect(model->delegateModel(), &QQmlDelegateModel::modelChanged,
q, &QQmlInstantiator::modelChanged);
}
}
QPointer<QQmlInstanceModel> model;

View File

@ -1160,6 +1160,10 @@ void QQuickItemViewPrivate::connectModel(QQuickItemView *q, QQmlDelegateModelPoi
QObjectPrivate::connect(
dataModel, &QQmlDelegateModel::delegateModelAccessChanged,
this, &QQuickItemViewPrivate::applyDelegateModelAccessChange);
if (ownModel) {
QObject::connect(dataModel, &QQmlDelegateModel::modelChanged,
q, &QQuickItemView::modelChanged);
}
}
emitCountChanged();
@ -1191,6 +1195,10 @@ void QQuickItemViewPrivate::disconnectModel(QQuickItemView *q, QQmlDelegateModel
QObjectPrivate::disconnect(
delegateModel, &QQmlDelegateModel::delegateModelAccessChanged,
this, &QQuickItemViewPrivate::applyDelegateModelAccessChange);
if (ownModel) {
QObject::disconnect(delegateModel, &QQmlDelegateModel::modelChanged,
q, &QQuickItemView::modelChanged);
}
}
}

View File

@ -470,6 +470,10 @@ void QQuickRepeaterPrivate::connectModel(QQuickRepeater *q, QQmlDelegateModelPoi
QObjectPrivate::connect(
dataModel, &QQmlDelegateModel::delegateModelAccessChanged,
this, &QQuickRepeaterPrivate::applyDelegateModelAccessChange);
if (ownModel) {
QObject::connect(dataModel, &QQmlDelegateModel::modelChanged,
q, &QQuickRepeater::modelChanged);
}
}
q->regenerate();
}
@ -493,6 +497,10 @@ void QQuickRepeaterPrivate::disconnectModel(QQuickRepeater *q, QQmlDelegateModel
QObjectPrivate::disconnect(
delegateModel, &QQmlDelegateModel::delegateModelAccessChanged,
this, &QQuickRepeaterPrivate::applyDelegateModelAccessChange);
if (ownModel) {
QObject::disconnect(delegateModel, &QQmlDelegateModel::modelChanged,
q, &QQuickRepeater::modelChanged);
}
}
}

View File

@ -477,6 +477,8 @@ void tst_qqmlinstantiator::delegateModelAccess()
QQmlInstantiator *instantiator = qobject_cast<QQmlInstantiator *>(object.data());
QVERIFY(instantiator);
QSignalSpy modelChangedSpy(instantiator, &QQmlInstantiator::modelChanged);
if (delegateKind == Delegate::Untyped && modelKind == Model::Array)
QSKIP("Properties of objects in arrays are not exposed as context properties");
@ -500,20 +502,34 @@ void tst_qqmlinstantiator::delegateModelAccess()
? access != QQmlDelegateModel::ReadOnly
: access == QQmlDelegateModel::ReadWrite;
// Only the array is actually updated itself. The other models are pointers
const bool writeShouldSignal = modelKind == Model::Kind::Array;
double expected = 11;
// Initial setting of the model, signals one update
int expectedModelUpdates = 1;
QCOMPARE(modelChangedSpy.count(), expectedModelUpdates);
QCOMPARE(delegate->property("immediateX").toDouble(), expected);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
if (modelWritable)
if (modelWritable) {
expected = 3;
if (writeShouldSignal)
++expectedModelUpdates;
}
QMetaObject::invokeMethod(delegate, "writeThroughModel");
QCOMPARE(delegate->property("immediateX").toDouble(), expected);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
QCOMPARE(modelChangedSpy.count(), expectedModelUpdates);
if (immediateWritable)
if (immediateWritable) {
expected = 1;
if (writeShouldSignal)
++expectedModelUpdates;
}
QMetaObject::invokeMethod(delegate, "writeImmediate");
@ -522,6 +538,7 @@ void tst_qqmlinstantiator::delegateModelAccess()
delegateKind == Delegate::Untyped ? expected : 1);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
QCOMPARE(modelChangedSpy.count(), expectedModelUpdates);
}
QTEST_MAIN(tst_qqmlinstantiator)

View File

@ -1435,6 +1435,8 @@ void tst_QQuickListView2::delegateModelAccess()
QQuickListView *listView = qobject_cast<QQuickListView *>(object.data());
QVERIFY(listView);
QSignalSpy modelChangedSpy(listView, &QQuickItemView::modelChanged);
if (delegateKind == Delegate::Untyped && modelKind == Model::Array)
QSKIP("Properties of objects in arrays are not exposed as context properties");
@ -1459,20 +1461,34 @@ void tst_QQuickListView2::delegateModelAccess()
? access != QQmlDelegateModel::ReadOnly
: access == QQmlDelegateModel::ReadWrite;
// Only the array is actually updated itself. The other models are pointers
const bool writeShouldSignal = modelKind == Model::Kind::Array;
double expected = 11;
// Initial setting of the model, signals one update
int expectedModelUpdates = 1;
QCOMPARE(modelChangedSpy.count(), expectedModelUpdates);
QCOMPARE(delegate->property("immediateX").toDouble(), expected);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
if (modelWritable)
if (modelWritable) {
expected = 3;
if (writeShouldSignal)
++expectedModelUpdates;
}
QMetaObject::invokeMethod(delegate, "writeThroughModel");
QCOMPARE(delegate->property("immediateX").toDouble(), expected);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
QCOMPARE(modelChangedSpy.count(), expectedModelUpdates);
if (immediateWritable)
if (immediateWritable) {
expected = 1;
if (writeShouldSignal)
++expectedModelUpdates;
}
QMetaObject::invokeMethod(delegate, "writeImmediate");
@ -1481,6 +1497,7 @@ void tst_QQuickListView2::delegateModelAccess()
delegateKind == Delegate::Untyped ? expected : 1);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
QCOMPARE(modelChangedSpy.count(), expectedModelUpdates);
}
enum RemovalPolicy {

View File

@ -1290,6 +1290,8 @@ void tst_QQuickRepeater::delegateModelAccess()
QQuickRepeater *repeater = qvariant_cast<QQuickRepeater *>(object->property("repeater"));
QVERIFY(repeater);
QSignalSpy modelChangedSpy(repeater, &QQuickRepeater::modelChanged);
if (delegateKind == Delegate::Untyped && modelKind == Model::Array)
QSKIP("Properties of objects in arrays are not exposed as context properties");
@ -1314,20 +1316,34 @@ void tst_QQuickRepeater::delegateModelAccess()
? access != QQmlDelegateModel::ReadOnly
: access == QQmlDelegateModel::ReadWrite;
// Only the array is actually updated itself. The other models are pointers
const bool writeShouldSignal = modelKind == Model::Kind::Array;
double expected = 11;
// Initial setting of the model, signals one update
int expectedModelUpdates = 1;
QCOMPARE(modelChangedSpy.count(), expectedModelUpdates);
QCOMPARE(delegate->property("immediateX").toDouble(), expected);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
if (modelWritable)
if (modelWritable) {
expected = 3;
if (writeShouldSignal)
++expectedModelUpdates;
}
QMetaObject::invokeMethod(delegate, "writeThroughModel");
QCOMPARE(delegate->property("immediateX").toDouble(), expected);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
QCOMPARE(modelChangedSpy.count(), expectedModelUpdates);
if (immediateWritable)
if (immediateWritable) {
expected = 1;
if (writeShouldSignal)
++expectedModelUpdates;
}
QMetaObject::invokeMethod(delegate, "writeImmediate");
@ -1336,6 +1352,7 @@ void tst_QQuickRepeater::delegateModelAccess()
delegateKind == Delegate::Untyped ? expected : 1);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
QCOMPARE(modelChangedSpy.count(), expectedModelUpdates);
}
QTEST_MAIN(tst_QQuickRepeater)