QQmlTableInstanceModel: always emit index changed when an item is reused

When reusing a delegate item, it can sometimes happen that the item
ends up being reused at the same location in the table as it had
before it was pooled. And in that case, we don't emit changes to
index, row and column since they technically didn't change.

The problem is that the model might have changed in-between, e.g if
a row has been removed. And in that case, row and column will, even
when unchanged, point to other parts of the model. So all bindings
needs to be reevaluated to ensure that the values they use are
refreshed.

This patch will therefore ensure that we always emit changes to
the mentioned properties when an item is reused, regardless if
they change or not.

Fixes: QTBUG-79209
Change-Id: Icec201a43a30b9f677303fbf652baf6487621deb
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
This commit is contained in:
Richard Moe Gustavsen 2019-10-14 15:06:17 +02:00
parent 1619b7d207
commit 35fdf3a7b7
6 changed files with 60 additions and 6 deletions

View File

@ -2123,7 +2123,7 @@ void QQmlDelegateModelItem::Dispose()
delete this;
}
void QQmlDelegateModelItem::setModelIndex(int idx, int newRow, int newColumn)
void QQmlDelegateModelItem::setModelIndex(int idx, int newRow, int newColumn, bool alwaysEmit)
{
const int prevIndex = index;
const int prevRow = row;
@ -2133,11 +2133,11 @@ void QQmlDelegateModelItem::setModelIndex(int idx, int newRow, int newColumn)
row = newRow;
column = newColumn;
if (idx != prevIndex)
if (idx != prevIndex || alwaysEmit)
emit modelIndexChanged();
if (row != prevRow)
if (row != prevRow || alwaysEmit)
emit rowChanged();
if (column != prevColumn)
if (column != prevColumn || alwaysEmit)
emit columnChanged();
}

View File

@ -129,7 +129,7 @@ public:
int modelRow() const { return row; }
int modelColumn() const { return column; }
int modelIndex() const { return index; }
virtual void setModelIndex(int idx, int newRow, int newColumn);
virtual void setModelIndex(int idx, int newRow, int newColumn, bool alwaysEmit = false);
virtual QV4::ReturnedValue get() { return QV4::QObjectWrapper::wrap(v4, this); }

View File

@ -342,9 +342,13 @@ void QQmlTableInstanceModel::reuseItem(QQmlDelegateModelItem *item, int newModel
{
// Update the context properties index, row and column on
// the delegate item, and inform the application about it.
// Note that we set alwaysEmit to true, to force all bindings
// to be reevaluated, even if the index didn't change (since
// the model can have changed size since last usage).
const bool alwaysEmit = true;
const int newRow = m_adaptorModel.rowAt(newModelIndex);
const int newColumn = m_adaptorModel.columnAt(newModelIndex);
item->setModelIndex(newModelIndex, newRow, newColumn);
item->setModelIndex(newModelIndex, newRow, newColumn, alwaysEmit);
// Notify the application that all 'dynamic'/role-based context data has
// changed as well (their getter function will use the updated index).

View File

@ -70,6 +70,7 @@ Item {
color: "lightgray"
border.width: 1
property string modelDataFromIndex: tableView.model.dataFromSerializedIndex(index)
property string modelDataBinding: modelData
Text {

View File

@ -63,6 +63,13 @@ public:
return QStringLiteral("%1").arg(index.row());
}
Q_INVOKABLE QVariant dataFromSerializedIndex(int index) const
{
if (modelData.contains(index))
return modelData.value(index);
return QString();
}
QHash<int, QByteArray> roleNames() const override
{
return { {Qt::DisplayRole, "display"} };
@ -102,6 +109,12 @@ public:
beginRemoveRows(parent, row, row + count - 1);
m_rows -= count;
for (int c = 0; c < m_columns; ++c) {
for (int r = 0; r < count; ++r) {
const int serializedIndex = (row + r) + (c * m_rows);
modelData.remove(serializedIndex);
}
}
endRemoveRows();
return true;
}

View File

@ -160,6 +160,7 @@ private slots:
void checkContextPropertiesQQmlListProperyModel_data();
void checkContextPropertiesQQmlListProperyModel();
void checkRowAndColumnChangedButNotIndex();
void checkThatWeAlwaysEmitChangedUponItemReused();
void checkChangingModelFromDelegate();
void checkRebuildViewportOnly();
void useDelegateChooserWithoutDefault();
@ -2083,6 +2084,41 @@ void tst_QQuickTableView::checkRowAndColumnChangedButNotIndex()
QCOMPARE(contextColumn, 1);
}
void tst_QQuickTableView::checkThatWeAlwaysEmitChangedUponItemReused()
{
// Check that we always emit changes to index when we reuse an item, even
// if it doesn't change. This is needed since the model can have changed
// row or column count while the item was in the pool, which means that
// any data referred to by the index property inside the delegate
// will change too. So we need to refresh any bindings to index.
// QTBUG-79209
LOAD_TABLEVIEW("plaintableview.qml");
TestModel model(1, 1);
tableView->setModel(QVariant::fromValue(&model));
model.setModelData(QPoint(0, 0), QSize(1, 1), "old value");
WAIT_UNTIL_POLISHED;
const auto reuseItem = tableViewPrivate->loadedTableItem(QPoint(0, 0))->item;
const auto context = qmlContext(reuseItem.data());
// Remove the cell/row that has "old value" as model data, and
// add a new one right after. The new cell will have the same
// index, but with no model data assigned.
// This change will not be detected by items in the pool. But since
// we emit indexChanged when the item is reused, it will be updated then.
model.removeRow(0);
model.insertRow(0);
WAIT_UNTIL_POLISHED;
QCOMPARE(context->contextProperty("index").toInt(), 0);
QCOMPARE(context->contextProperty("row").toInt(), 0);
QCOMPARE(context->contextProperty("column").toInt(), 0);
QCOMPARE(context->contextProperty("modelDataFromIndex").toString(), "");
}
void tst_QQuickTableView::checkChangingModelFromDelegate()
{
// Check that we don't restart a rebuild of the table