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:
parent
db2792dd86
commit
8bc307e8ed
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue