From 1148d3acead3c13072876b873fb4ee9440921943 Mon Sep 17 00:00:00 2001 From: Michael Brasser Date: Tue, 1 Mar 2016 21:14:48 -0600 Subject: Add delayed property to Binding. Provide a way to avoid potentially expensive or unexpected intermediate values. [ChangeLog][Binding] Add delayed property to Binding as a way to avoid potentially expensive or unexpected intermediate values. Change-Id: I6aaca570859cc1344eeb9c9f19f32660e8c0b4e0 Reviewed-by: Robin Burchell --- src/qml/qml/qqmlengine.cpp | 1 + src/qml/types/qqmlbind.cpp | 57 ++++++++++++++++++++++-- src/qml/types/qqmlbind_p.h | 5 +++ tests/auto/qml/qqmlbinding/data/delayed.qml | 26 +++++++++++ tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp | 22 +++++++++ tests/auto/qml/qqmlengine/data/qtqmlModule.4.qml | 2 +- tests/auto/qml/qqmlengine/tst_qqmlengine.cpp | 4 +- 7 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 tests/auto/qml/qqmlbinding/data/delayed.qml diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index c14b9a0966..63d3feee1e 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -179,6 +179,7 @@ void QQmlEnginePrivate::registerBaseTypes(const char *uri, int versionMajor, int qmlRegisterType(uri,versionMajor,versionMinor,"Component"); qmlRegisterType(uri,versionMajor,versionMinor,"QtObject"); qmlRegisterType(uri, versionMajor, versionMinor,"Binding"); + qmlRegisterType(uri, versionMajor, (versionMinor < 8 ? 8 : versionMinor), "Binding"); //Only available in >=2.8 qmlRegisterType(uri, versionMajor, (versionMinor < 3 ? 3 : versionMinor), "Connections"); //Only available in >=2.3 qmlRegisterType(uri, versionMajor, versionMinor,"Connections"); qmlRegisterType(uri, versionMajor, versionMinor,"Timer"); diff --git a/src/qml/types/qqmlbind.cpp b/src/qml/types/qqmlbind.cpp index ed9a8533c0..3102da3f20 100644 --- a/src/qml/types/qqmlbind.cpp +++ b/src/qml/types/qqmlbind.cpp @@ -51,6 +51,7 @@ #include #include +#include #include @@ -59,16 +60,18 @@ QT_BEGIN_NAMESPACE class QQmlBindPrivate : public QObjectPrivate { public: - QQmlBindPrivate() : componentComplete(true), obj(0) {} + QQmlBindPrivate() : obj(0), componentComplete(true), delayed(false), pendingEval(false) {} ~QQmlBindPrivate() { } QQmlNullableValue when; - bool componentComplete; QPointer obj; QString propName; QQmlNullableValue value; QQmlProperty prop; QQmlAbstractBinding::Ptr prevBind; + bool componentComplete:1; + bool delayed:1; + bool pendingEval:1; void validate(QObject *binding) const; }; @@ -281,7 +284,42 @@ void QQmlBind::setValue(const QVariant &v) { Q_D(QQmlBind); d->value = v; - eval(); + prepareEval(); +} + +/*! + \qmlproperty bool QtQml::Binding::delayed + + This property holds whether the binding should be delayed. + + A delayed binding will not immediately update the target, but rather wait + until the event queue has been cleared. This can be used as an optimization, + or to prevent intermediary values from being assigned. + + \code + Binding { + target: contactName; property: 'text' + value: givenName + " " + familyName; when: list.ListView.isCurrentItem + delayed: true + } + \endcode +*/ +bool QQmlBind::delayed() const +{ + Q_D(const QQmlBind); + return d->delayed; +} + +void QQmlBind::setDelayed(bool delayed) +{ + Q_D(QQmlBind); + if (d->delayed == delayed) + return; + + d->delayed = delayed; + + if (!d->delayed) + eval(); } void QQmlBind::setTarget(const QQmlProperty &p) @@ -307,9 +345,22 @@ void QQmlBind::componentComplete() eval(); } +void QQmlBind::prepareEval() +{ + Q_D(QQmlBind); + if (d->delayed) { + if (!d->pendingEval) + QTimer::singleShot(0, this, &QQmlBind::eval); + d->pendingEval = true; + } else { + eval(); + } +} + void QQmlBind::eval() { Q_D(QQmlBind); + d->pendingEval = false; if (!d->prop.isValid() || d->value.isNull || !d->componentComplete) return; diff --git a/src/qml/types/qqmlbind_p.h b/src/qml/types/qqmlbind_p.h index 581995af56..77939a40bc 100644 --- a/src/qml/types/qqmlbind_p.h +++ b/src/qml/types/qqmlbind_p.h @@ -68,6 +68,7 @@ class Q_AUTOTEST_EXPORT QQmlBind : public QObject, public QQmlPropertyValueSourc Q_PROPERTY(QString property READ property WRITE setProperty) Q_PROPERTY(QVariant value READ value WRITE setValue) Q_PROPERTY(bool when READ when WRITE setWhen) + Q_PROPERTY(bool delayed READ delayed WRITE setDelayed REVISION 8) public: QQmlBind(QObject *parent=0); @@ -85,12 +86,16 @@ public: QVariant value() const; void setValue(const QVariant &); + bool delayed() const; + void setDelayed(bool); + protected: virtual void setTarget(const QQmlProperty &); virtual void classBegin(); virtual void componentComplete(); private: + void prepareEval(); void eval(); }; diff --git a/tests/auto/qml/qqmlbinding/data/delayed.qml b/tests/auto/qml/qqmlbinding/data/delayed.qml new file mode 100644 index 0000000000..6f8281cc33 --- /dev/null +++ b/tests/auto/qml/qqmlbinding/data/delayed.qml @@ -0,0 +1,26 @@ +import QtQuick 2.8 + +Item { + width: 400 + height: 400 + + property int changeCount: 0 + + property string text1 + property string text2 + + function updateText() { + text1 = "Hello" + text2 = "World" + } + + Text { + anchors.centerIn: parent + Binding on text { + value: text1 + " " + text2 + delayed: true + } + onTextChanged: ++changeCount + } +} + diff --git a/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp b/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp index 3e8dfbdb12..6f1d82eca5 100644 --- a/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp +++ b/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp @@ -49,6 +49,7 @@ private slots: void warningOnReadOnlyProperty(); void disabledOnUnknownProperty(); void disabledOnReadonlyProperty(); + void delayed(); private: QQmlEngine engine; @@ -281,6 +282,27 @@ void tst_qqmlbinding::disabledOnReadonlyProperty() QCOMPARE(messageHandler.messages().count(), 0); } +void tst_qqmlbinding::delayed() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("delayed.qml")); + QQuickItem *item = qobject_cast(c.create()); + + QVERIFY(item != 0); + // update on creation + QCOMPARE(item->property("changeCount").toInt(), 1); + + QMetaObject::invokeMethod(item, "updateText"); + // doesn't update immediately + QCOMPARE(item->property("changeCount").toInt(), 1); + + QCoreApplication::processEvents(); + // only updates once (non-delayed would update twice) + QCOMPARE(item->property("changeCount").toInt(), 2); + + delete item; +} + QTEST_MAIN(tst_qqmlbinding) #include "tst_qqmlbinding.moc" diff --git a/tests/auto/qml/qqmlengine/data/qtqmlModule.4.qml b/tests/auto/qml/qqmlengine/data/qtqmlModule.4.qml index 9b9b7922da..94ee46ddf0 100644 --- a/tests/auto/qml/qqmlengine/data/qtqmlModule.4.qml +++ b/tests/auto/qml/qqmlengine/data/qtqmlModule.4.qml @@ -1,4 +1,4 @@ -import QtQml 2.5 +import QtQml 2.50 QtObject { } diff --git a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp index 3208745c5c..3af1cf46b3 100644 --- a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp +++ b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp @@ -615,9 +615,9 @@ void tst_qqmlengine::qtqmlModule_data() << QString(testFileUrl("qtqmlModule.3.qml").toString() + QLatin1String(":1 module \"QtQml\" version 1.0 is not installed\n")) << QStringList(); - QTest::newRow("import QtQml of incorrect version (2.5)") + QTest::newRow("import QtQml of incorrect version (2.50)") << testFileUrl("qtqmlModule.4.qml") - << QString(testFileUrl("qtqmlModule.4.qml").toString() + QLatin1String(":1 module \"QtQml\" version 2.5 is not installed\n")) + << QString(testFileUrl("qtqmlModule.4.qml").toString() + QLatin1String(":1 module \"QtQml\" version 2.50 is not installed\n")) << QStringList(); QTest::newRow("QtQml 2.0 module provides Component, QtObject, Connections, Binding and Timer") -- cgit v1.2.3