API Review: Expose delegateModelAccess from QQmlInstantiator

It mirrors the same property from any own delegate model. If you
explicitly set an external DelegateModel to be used by the instantiator,
that one's delegateModelAccess is used. This is in line with how the
delegates behave.

The lack of the property in Instantiator is an API inconsistency and
therefore the fix has to be picked back to 6.10.

[ChangeLog][QtQml] Instantiator now has a new property
delegateModelAccess. Setting it to DelegateModel.ReadWrite allows you to
write values into the model via required properties just as you could
with context properties.

Pick-to: 6.10
Task-number: QTBUG-132420
Change-Id: I44b480182339bff8aef8aba385d2bde63f825ff9
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Ulf Hermann 2025-06-19 08:07:42 +02:00
parent db2792dd86
commit 8bc307e8ed
7 changed files with 288 additions and 17 deletions

View File

@ -22,10 +22,7 @@ QQmlInstantiatorPrivate::QQmlInstantiatorPrivate()
#if QT_CONFIG(qml_delegate_model)
, ownModel(false)
#endif
, requestedIndex(-1)
, model(QVariant(1))
, instanceModel(nullptr)
, delegate(nullptr)
{
}
@ -175,6 +172,7 @@ void QQmlInstantiatorPrivate::makeModel()
instanceModel = delegateModel;
ownModel = true;
delegateModel->setDelegate(delegate);
delegateModel->setDelegateModelAccess(delegateModelAccess);
delegateModel->classBegin(); //Pretend it was made in QML
if (componentComplete)
delegateModel->componentComplete();
@ -403,6 +401,39 @@ void QQmlInstantiator::setModel(const QVariant &v)
emit modelChanged();
}
#if QT_CONFIG(qml_delegate_model)
/*!
\qmlproperty enumeration QtQml.Models::Instantiator::delegateModelAccess
\include delegatemodelaccess.qdocinc
*/
QQmlDelegateModel::DelegateModelAccess QQmlInstantiator::delegateModelAccess() const
{
Q_D(const QQmlInstantiator);
return d->delegateModelAccess;
}
void QQmlInstantiator::setDelegateModelAccess(
QQmlDelegateModel::DelegateModelAccess delegateModelAccess)
{
Q_D(QQmlInstantiator);
if (delegateModelAccess == d->delegateModelAccess)
return;
d->delegateModelAccess = delegateModelAccess;
emit delegateModelAccessChanged();
if (!d->ownModel)
return;
if (QQmlDelegateModel *dModel = qobject_cast<QQmlDelegateModel*>(d->instanceModel))
dModel->setDelegateModelAccess(delegateModelAccess);
if (d->componentComplete)
d->regenerate();
}
#endif
/*!
\qmlproperty QtObject QtQml.Models::Instantiator::object

View File

@ -17,7 +17,9 @@
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qqmlparserstatus.h>
#include <QtQmlModels/private/qtqmlmodelsglobal_p.h>
#include <private/qqmldelegatemodel_p.h>
#include <private/qtqmlmodelsglobal_p.h>
QT_REQUIRE_CONFIG(qml_object_model);
@ -35,6 +37,10 @@ class Q_QMLMODELS_EXPORT QQmlInstantiator : public QObject, public QQmlParserSta
Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
Q_PROPERTY(QObject *object READ object NOTIFY objectChanged)
#if QT_CONFIG(qml_delegate_model)
Q_PROPERTY(QQmlDelegateModel::DelegateModelAccess delegateModelAccess READ delegateModelAccess
WRITE setDelegateModelAccess NOTIFY delegateModelAccessChanged REVISION(6, 11) FINAL)
#endif
Q_CLASSINFO("DefaultProperty", "delegate")
QML_NAMED_ELEMENT(Instantiator)
QML_ADDED_IN_VERSION(2, 1)
@ -57,6 +63,11 @@ public:
QVariant model() const;
void setModel(const QVariant &v);
#if QT_CONFIG(qml_delegate_model)
QQmlDelegateModel::DelegateModelAccess delegateModelAccess() const;
void setDelegateModelAccess(QQmlDelegateModel::DelegateModelAccess delegateModelAccess);
#endif
QObject *object() const;
Q_INVOKABLE QObject *objectAt(int index) const;
@ -75,6 +86,10 @@ Q_SIGNALS:
void objectAdded(int index, QObject* object);
void objectRemoved(int index, QObject* object);
#if QT_CONFIG(qml_delegate_model)
Q_REVISION(6, 11) void delegateModelAccessChanged();
#endif
private:
Q_DISABLE_COPY(QQmlInstantiator)
Q_DECLARE_PRIVATE(QQmlInstantiator)

View File

@ -72,11 +72,12 @@ public:
bool async:1;
#if QT_CONFIG(qml_delegate_model)
bool ownModel:1;
QQmlDelegateModel::DelegateModelAccess delegateModelAccess = QQmlDelegateModel::Qt5ReadWrite;
#endif
int requestedIndex;
int requestedIndex = -1;
QVariant model;
QQmlInstanceModel *instanceModel;
QQmlComponent *delegate;
QQmlInstanceModel *instanceModel = nullptr;
QQmlComponent *delegate = nullptr;
QVector<QPointer<QObject> > objects;
};

View File

@ -21,6 +21,7 @@ list(APPEND test_data ${test_data_glob})
qt_internal_add_test(tst_qqmlinstantiator
SOURCES
delegatemodelkinds.h
stringmodel.h
tst_qqmlinstantiator.cpp
LIBRARIES

View File

@ -0,0 +1,91 @@
import QtQml
import Test
Instantiator {
id: root
objectName: "hundred"
property Component typedDelegate: QtObject {
objectName: "ten"
required property QtObject model
required property real a
property real immediateX: a
property real modelX: model.a
function writeImmediate() {
a = 1;
}
function writeThroughModel() {
model.a = 3;
}
}
property Component untypedDelegate: QtObject {
objectName: "ten"
property real immediateX: a
property real modelX: model.a
function writeImmediate() {
a = 1;
}
function writeThroughModel() {
model.a = 3;
}
}
property ListModel singularModel: ListModel {
ListElement {
a: 11
}
ListElement {
a: 12
}
}
property ListModel listModel: ListModel {
ListElement {
a: 11
y: 12
}
ListElement {
a: 15
y: 16
}
}
property var array: [
{a: 11, y: 12}, {a: 19, y: 20}
]
property QtObject object: QtObject {
property int a: 11
property int y: 12
}
property int modelIndex: Model.None
property int delegateIndex: Delegate.None
model: {
switch (modelIndex) {
case Model.Singular: return singularModel
case Model.List: return listModel
case Model.Array: return array
case Model.Object: return object
}
return undefined;
}
delegate: {
switch (delegateIndex) {
case Delegate.Untyped: return untypedDelegate
case Delegate.Typed: return typedDelegate
}
return null
}
}

View File

@ -0,0 +1,36 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef DELEGATEMODELKINDS_H
#define DELEGATEMODELKINDS_H
#include <QtCore/qobject.h>
#include <QtQml/qqml.h>
namespace Model {
Q_NAMESPACE
QML_ELEMENT
enum Kind : qint8
{
None = -1,
Singular,
List,
Array,
Object
};
Q_ENUM_NS(Kind)
}
namespace Delegate {
Q_NAMESPACE
QML_ELEMENT
enum Kind : qint8
{
None = -1,
Untyped,
Typed
};
Q_ENUM_NS(Kind)
}
#endif // DELEGATEMODELKINDS_H

View File

@ -1,18 +1,23 @@
// Copyright (C) 2016 Research In Motion.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <qtest.h>
#include <QSignalSpy>
#include <QSortFilterProxyModel>
#include <QDebug>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQmlModels/private/qqmlinstantiator_p.h>
#include <QtQml/qqmlcontext.h>
#include <QtQml/qqmlincubator.h>
#include <QtQuickTestUtils/private/qmlutils_p.h>
#include "delegatemodelkinds.h"
#include "stringmodel.h"
#include <private/qmlutils_p.h>
#include <private/qqmlinstantiator_p.h>
#include <QtTest/qsignalspy.h>
#include <QtTest/qtest.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qqmlcontext.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlincubator.h>
#include <QtCore/qdebug.h>
#include <QtCore/qsortfilterproxymodel.h>
class tst_qqmlinstantiator: public QQmlDataTest
{
Q_OBJECT
@ -40,11 +45,16 @@ private slots:
void listDataDestruction();
void setDelegateAfterModel();
void delegateModelAccess_data();
void delegateModelAccess();
};
tst_qqmlinstantiator::tst_qqmlinstantiator()
: QQmlDataTest(QT_QMLTEST_DATADIR)
{
qmlRegisterNamespaceAndRevisions(&Model::staticMetaObject, "Test", 1);
qmlRegisterNamespaceAndRevisions(&Delegate::staticMetaObject, "Test", 1);
}
void tst_qqmlinstantiator::createNone()
@ -428,6 +438,92 @@ void tst_qqmlinstantiator::removeDuringModelChange()
QCOMPARE(proxyModel.rowCount(), 0);
}
template<typename Enum>
const char *enumKey(Enum value) {
const QMetaObject *mo = qt_getEnumMetaObject(value);
const QMetaEnum metaEnum = mo->enumerator(mo->indexOfEnumerator(qt_getEnumName(value)));
return metaEnum.valueToKey(value);
}
void tst_qqmlinstantiator::delegateModelAccess_data()
{
QTest::addColumn<QQmlDelegateModel::DelegateModelAccess>("access");
QTest::addColumn<Model::Kind>("modelKind");
QTest::addColumn<Delegate::Kind>("delegateKind");
using Access = QQmlDelegateModel::DelegateModelAccess;
for (auto access : { Access::Qt5ReadWrite, Access::ReadOnly, Access::ReadWrite }) {
for (auto model : { Model::Singular, Model::List, Model::Array, Model::Object }) {
for (auto delegate : { Delegate::Untyped, Delegate::Typed }) {
QTest::addRow("%s-%s-%s", enumKey(access), enumKey(model), enumKey(delegate))
<< access << model << delegate;
}
}
}
}
void tst_qqmlinstantiator::delegateModelAccess()
{
QFETCH(QQmlDelegateModel::DelegateModelAccess, access);
QFETCH(Model::Kind, modelKind);
QFETCH(Delegate::Kind, delegateKind);
QQmlEngine engine;
const QUrl url = testFileUrl("delegateModelAccess.qml");
QQmlComponent c(&engine, url);
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QScopedPointer<QObject> object(c.create());
QQmlInstantiator *instantiator = qobject_cast<QQmlInstantiator *>(object.data());
QVERIFY(instantiator);
if (delegateKind == Delegate::Untyped && modelKind == Model::Array)
QSKIP("Properties of objects in arrays are not exposed as context properties");
if (access == QQmlDelegateModel::ReadOnly) {
const QRegularExpression message(
url.toString() + ":[0-9]+: TypeError: Cannot assign to read-only property \"a\"");
QTest::ignoreMessage(QtWarningMsg, message);
if (delegateKind == Delegate::Untyped)
QTest::ignoreMessage(QtWarningMsg, message);
}
object->setProperty("delegateModelAccess", access);
object->setProperty("modelIndex", modelKind);
object->setProperty("delegateIndex", delegateKind);
QObject *delegate = instantiator->objectAt(0);
QVERIFY(delegate);
const bool modelWritable = access != QQmlDelegateModel::ReadOnly;
const bool immediateWritable = (delegateKind == Delegate::Untyped)
? access != QQmlDelegateModel::ReadOnly
: access == QQmlDelegateModel::ReadWrite;
double expected = 11;
QCOMPARE(delegate->property("immediateX").toDouble(), expected);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
if (modelWritable)
expected = 3;
QMetaObject::invokeMethod(delegate, "writeThroughModel");
QCOMPARE(delegate->property("immediateX").toDouble(), expected);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
if (immediateWritable)
expected = 1;
QMetaObject::invokeMethod(delegate, "writeImmediate");
// Writes to required properties always succeed, but might not be propagated to the model
QCOMPARE(delegate->property("immediateX").toDouble(),
delegateKind == Delegate::Untyped ? expected : 1);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
}
QTEST_MAIN(tst_qqmlinstantiator)
#include "tst_qqmlinstantiator.moc"