aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard Moe Gustavsen <richard.gustavsen@qt.io>2018-09-14 10:03:27 +0200
committerRichard Moe Gustavsen <richard.gustavsen@qt.io>2018-09-17 07:51:20 +0000
commitf4e96bf319b14bc64d9c92551303991b4f303846 (patch)
tree8c893a6a22b97519ded0d62d1ab64cdc1d572bf6
parent1dac47c1418b44cf4a56b42bfca2b277795fd213 (diff)
QQuickTableView: build the table when the component is finalized
componentComplete() is called on us after all static values have been assigned, but before bindings to any ancestors have been evaluated. Especially this means that if our size is bound to the parents size, it will not be ready at that point. Since we cannot build the table without knowing our own size, we waited for the updatePolish() call before we started to build the table. The problem with that strategy, is that any asynchronous loaders that TableView might be inside would already be finished by the time we received the updatePolish() call. The result would be that we ended up loading all the delegate items synchronously instead of asynchronously. (As soon as a loader has finished loading the initial item, async loading will no longer be used). This patch will therefore add a componentFinalized function that gets called after all bindings have been evaluated, but before the loader has finished. When receiving this call, we load the delegate items (and build the table). A nice side effect is that the table will also be ready by the time Component.onCompeted is emitted to the QML app. This means that e.g contentWidth/Height has valid values. Change-Id: Ief92d2fecfaea54f6191da116ed4ba79cc673b01 Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
-rw-r--r--src/quick/items/qquicktableview.cpp33
-rw-r--r--src/quick/items/qquicktableview_p.h3
-rw-r--r--src/quick/items/qquicktableview_p_p.h3
-rw-r--r--tests/auto/quick/qquicktableview/data/asyncloader.qml56
-rw-r--r--tests/auto/quick/qquicktableview/data/asyncplain.qml96
-rw-r--r--tests/auto/quick/qquicktableview/tst_qquicktableview.cpp63
6 files changed, 250 insertions, 4 deletions
diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp
index 67a9a3a7c2..1e2ecf9047 100644
--- a/src/quick/items/qquicktableview.cpp
+++ b/src/quick/items/qquicktableview.cpp
@@ -1946,6 +1946,39 @@ void QQuickTableView::viewportMoved(Qt::Orientations orientation)
d->updatePolish();
}
+void QQuickTableViewPrivate::_q_componentFinalized()
+{
+ // Now that all bindings are evaluated, and we know
+ // our final geometery, we can build the table.
+ qCDebug(lcTableViewDelegateLifecycle);
+ updatePolish();
+}
+
+void QQuickTableViewPrivate::registerCallbackWhenBindingsAreEvaluated()
+{
+ // componentComplete() is called on us after all static values have been assigned, but
+ // before bindings to any anchestors has been evaluated. Especially this means that
+ // if our size is bound to the parents size, it will still be empty at that point.
+ // And we cannot build the table without knowing our own size. We could wait until we
+ // got the first updatePolish() callback, but at that time, any asynchronous loaders that we
+ // might be inside have already finished loading, which means that we would load all
+ // the delegate items synchronously instead of asynchronously. We therefore add a componentFinalized
+ // function that gets called after all the bindings we rely on has been evaluated.
+ // When receiving this call, we load the delegate items (and build the table).
+ Q_Q(QQuickTableView);
+ QQmlEnginePrivate *engPriv = QQmlEnginePrivate::get(qmlEngine(q));
+ static int finalizedIdx = -1;
+ if (finalizedIdx < 0)
+ finalizedIdx = q->metaObject()->indexOfSlot("_q_componentFinalized()");
+ engPriv->registerFinalizeCallback(q, finalizedIdx);
+}
+
+void QQuickTableView::componentComplete()
+{
+ QQuickFlickable::componentComplete();
+ d_func()->registerCallbackWhenBindingsAreEvaluated();
+}
+
#include "moc_qquicktableview_p.cpp"
QT_END_NAMESPACE
diff --git a/src/quick/items/qquicktableview_p.h b/src/quick/items/qquicktableview_p.h
index 45e20027ba..9fcd4c6c17 100644
--- a/src/quick/items/qquicktableview_p.h
+++ b/src/quick/items/qquicktableview_p.h
@@ -128,10 +128,13 @@ Q_SIGNALS:
protected:
void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
void viewportMoved(Qt::Orientations orientation) override;
+ void componentComplete() override;
private:
Q_DISABLE_COPY(QQuickTableView)
Q_DECLARE_PRIVATE(QQuickTableView)
+
+ Q_PRIVATE_SLOT(d_func(), void _q_componentFinalized())
};
class Q_QUICK_PRIVATE_EXPORT QQuickTableViewAttached : public QObject
diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h
index 6bf3de76df..4f5c819887 100644
--- a/src/quick/items/qquicktableview_p_p.h
+++ b/src/quick/items/qquicktableview_p_p.h
@@ -353,6 +353,9 @@ public:
void columnsRemovedCallback(const QModelIndex &parent, int begin, int end);
void modelResetCallback();
+ void _q_componentFinalized();
+ void registerCallbackWhenBindingsAreEvaluated();
+
inline QString tableLayoutToString() const;
void dumpTable() const;
};
diff --git a/tests/auto/quick/qquicktableview/data/asyncloader.qml b/tests/auto/quick/qquicktableview/data/asyncloader.qml
new file mode 100644
index 0000000000..ba99430416
--- /dev/null
+++ b/tests/auto/quick/qquicktableview/data/asyncloader.qml
@@ -0,0 +1,56 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.12
+
+Item {
+ width: 640
+ height: 450
+
+ property alias loader: loader
+ property TableView tableView: loader.item ? loader.item.tableView : null
+ property string loaderSource: ""
+
+ Loader {
+ id: loader
+ anchors.fill: parent
+ source: Qt.resolvedUrl(loaderSource)
+ asynchronous: true
+ }
+}
diff --git a/tests/auto/quick/qquicktableview/data/asyncplain.qml b/tests/auto/quick/qquicktableview/data/asyncplain.qml
new file mode 100644
index 0000000000..df09d3e276
--- /dev/null
+++ b/tests/auto/quick/qquicktableview/data/asyncplain.qml
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.12
+import QtQuick.Window 2.3
+import TestModel 0.1
+
+Item {
+ id: root
+ width: 640
+ height: 450
+
+ property alias tableView: tableView
+ property Loader loader: parent
+
+ property int statusWhenDelegate0_0Created: Loader.Null
+ property int statusWhenDelegate5_5Created: Loader.Null
+
+ property real tableViewWidthWhileBuilding: -1
+ property real tableViewHeightWhileBuilding: -1
+
+ TableView {
+ id: tableView
+ anchors.fill: parent
+ clip: true
+ delegate: tableViewDelegate
+ columnSpacing: 1
+ rowSpacing: 1
+ model: TestModel {
+ rowCount: 100
+ columnCount: 100
+ }
+ }
+
+ Component {
+ id: tableViewDelegate
+ Rectangle {
+ implicitWidth: 100
+ implicitHeight: 50
+ color: "lightgray"
+ border.width: 1
+
+ Text {
+ anchors.centerIn: parent
+ text: modelData
+ }
+
+ Component.onCompleted: {
+ if (row === 0 && column === 0) {
+ statusWhenDelegate0_0Created = loader.status
+ tableViewWidthWhileBuilding = tableView.width
+ tableViewHeightWhileBuilding = tableView.height
+ }
+ else if (row === 5 && column === 5)
+ statusWhenDelegate5_5Created = loader.status
+ }
+ }
+ }
+
+}
diff --git a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp
index a1951bdf90..039fb91da0 100644
--- a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp
+++ b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp
@@ -31,6 +31,7 @@
#include <QtQuick/qquickview.h>
#include <QtQuick/private/qquicktableview_p.h>
#include <QtQuick/private/qquicktableview_p_p.h>
+#include <QtQuick/private/qquickloader_p.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcontext.h>
@@ -55,15 +56,28 @@ static const char *kModelDataBindingProp = "modelDataBinding";
Q_DECLARE_METATYPE(QMarginsF);
-#define LOAD_TABLEVIEW(fileName) \
- view->setSource(testFileUrl(fileName)); \
- view->show(); \
- QVERIFY(QTest::qWaitForWindowActive(view)); \
+#define DECLARE_TABLEVIEW_VARIABLES \
auto tableView = view->rootObject()->property(kTableViewPropName).value<QQuickTableView *>(); \
QVERIFY(tableView); \
auto tableViewPrivate = QQuickTableViewPrivate::get(tableView); \
Q_UNUSED(tableViewPrivate)
+#define LOAD_TABLEVIEW(fileName) \
+ view->setSource(testFileUrl(fileName)); \
+ view->show(); \
+ QVERIFY(QTest::qWaitForWindowActive(view)); \
+ DECLARE_TABLEVIEW_VARIABLES
+
+#define LOAD_TABLEVIEW_ASYNC(fileName) \
+ view->setSource(testFileUrl("asyncloader.qml")); \
+ view->show(); \
+ QVERIFY(QTest::qWaitForWindowActive(view)); \
+ auto loader = view->rootObject()->property("loader").value<QQuickLoader *>(); \
+ loader->setSource(QUrl::fromLocalFile("data/" fileName)); \
+ QTRY_VERIFY(loader->item()); \
+ QCOMPARE(loader->status(), QQuickLoader::Status::Ready); \
+ DECLARE_TABLEVIEW_VARIABLES
+
#define WAIT_UNTIL_POLISHED \
QVERIFY(tableViewPrivate->polishScheduled); \
QTRY_VERIFY(!tableViewPrivate->polishScheduled)
@@ -139,6 +153,7 @@ private slots:
void checkChangingModelFromDelegate();
void checkRebuildViewportOnly();
void useDelegateChooserWithoutDefault();
+ void checkTableviewInsideAsyncLoader();
};
tst_QQuickTableView::tst_QQuickTableView()
@@ -1815,6 +1830,46 @@ void tst_QQuickTableView::useDelegateChooserWithoutDefault()
WAIT_UNTIL_POLISHED;
};
+void tst_QQuickTableView::checkTableviewInsideAsyncLoader()
+{
+ // Check that you can put a TableView inside an async Loader, and
+ // that the delegate items are created before the loader is ready.
+ LOAD_TABLEVIEW_ASYNC("asyncplain.qml");
+
+ // At this point the Loader has finished
+ QCOMPARE(loader->status(), QQuickLoader::Ready);
+
+ // Check that TableView has finished building
+ QCOMPARE(tableViewPrivate->rebuildScheduled, false);
+ QCOMPARE(tableViewPrivate->rebuildState, QQuickTableViewPrivate::RebuildState::Done);
+
+ // Check that all expected delegate items have been loaded
+ const qreal delegateWidth = 100;
+ const qreal delegateHeight = 50;
+ int expectedColumns = qCeil(tableView->width() / delegateWidth);
+ int expectedRows = qCeil(tableView->height() / delegateHeight);
+ QCOMPARE(tableViewPrivate->loadedTable.width(), expectedColumns);
+ QCOMPARE(tableViewPrivate->loadedTable.height(), expectedRows);
+
+ // Check that the loader was still in a loading state while TableView was creating
+ // delegate items. If we delayed creating delegate items until we got the first
+ // updatePolish() callback in QQuickTableView, this would not be the case.
+ auto statusWhenDelegate0_0Completed = qvariant_cast<QQuickLoader::Status>(
+ loader->item()->property("statusWhenDelegate0_0Created"));
+ auto statusWhenDelegate5_5Completed = qvariant_cast<QQuickLoader::Status>(
+ loader->item()->property("statusWhenDelegate5_5Created"));
+ QCOMPARE(statusWhenDelegate0_0Completed, QQuickLoader::Loading);
+ QCOMPARE(statusWhenDelegate5_5Completed, QQuickLoader::Loading);
+
+ // Check that TableView had a valid geometry when we started to build. If the build
+ // was started too early (e.g upon QQuickTableView::componentComplete), width and
+ // height would still be 0 since the bindings would not have been evaluated yet.
+ qreal width = loader->item()->property("tableViewWidthWhileBuilding").toReal();
+ qreal height = loader->item()->property("tableViewHeightWhileBuilding").toReal();
+ QVERIFY(width > 0);
+ QVERIFY(height > 0);
+};
+
QTEST_MAIN(tst_QQuickTableView)
#include "tst_qquicktableview.moc"