QQuickTableView: Expose internal model changes via the model property

If the delegate changes the model, those changes need to be visible in
the "model" property of the view. To this end, use
QQmlTableInstanceModel's model variant instead of the assigned one once
it has been synchronized.

The inner change signaling from the delegates to the view will be
added in a separate change.

Pick-to: 6.10
Task-number: QTBUG-139941
Change-Id: I1296fa2c886dad063b6b39defef56cb7faf1e943
Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
This commit is contained in:
Ulf Hermann 2025-09-11 14:01:58 +02:00
parent 7105eb6d0d
commit a5ad373e6e
6 changed files with 71 additions and 4 deletions

View File

@ -452,7 +452,7 @@ QVariant QQmlTableInstanceModel::model() const
return m_adaptorModel.model();
}
void QQmlTableInstanceModel::setModel(const QVariant &model)
void QQmlTableInstanceModel::forceSetModel(const QVariant &model)
{
// Pooled items are still accessible/alive for the application, and
// needs to stay in sync with the model. So we need to drain the pool
@ -469,6 +469,16 @@ void QQmlTableInstanceModel::setModel(const QVariant &model)
}
}
void QQmlTableInstanceModel::setModel(const QVariant &model)
{
if (m_adaptorModel.model() == model)
return;
forceSetModel(model);
emit modelChanged();
}
void QQmlTableInstanceModel::dataChangedCallback(const QModelIndex &begin, const QModelIndex &end, const QVector<int> &roles)
{
// This function is called when model data has changed. In that case, we tell the adaptor model
@ -494,8 +504,11 @@ void QQmlTableInstanceModel::modelAboutToBeResetCallback()
auto const aim = abstractItemModel();
auto oldRoleNames = aim->roleNames();
QObject::connect(aim, &QAbstractItemModel::modelReset, this, [this, aim, oldRoleNames](){
if (oldRoleNames != aim->roleNames())
setModel(model());
if (oldRoleNames != aim->roleNames()) {
// We refresh the model, but without sending any signals. The actual model object
// stays the same after all.
forceSetModel(model());
}
}, Qt::SingleShotConnection);
}

View File

@ -102,6 +102,9 @@ public:
QQmlDelegateModelItem *getModelItem(int index);
signals:
void modelChanged();
private:
enum DestructionMode {
Deferred,
@ -129,6 +132,7 @@ private:
void dataChangedCallback(const QModelIndex &begin, const QModelIndex &end, const QVector<int> &roles);
void modelAboutToBeResetCallback();
void forceSetModel(const QVariant &model);
static bool isDoneIncubating(QQmlDelegateModelItem *modelItem);
static void deleteModelItemLater(QQmlDelegateModelItem *modelItem);

View File

@ -4573,12 +4573,17 @@ void QQuickTableViewPrivate::syncDelegateModelAccess()
QVariant QQuickTableViewPrivate::modelImpl() const
{
return assignedModel;
if (needsModelSynchronization)
return assignedModel;
if (tableModel)
return tableModel->model();
return QVariant::fromValue(model);
}
void QQuickTableViewPrivate::setModelImpl(const QVariant &newModel)
{
assignedModel = newModel;
needsModelSynchronization = true;
scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::All);
emit q_func()->modelChanged();
}
@ -4612,6 +4617,7 @@ void QQuickTableViewPrivate::syncModel()
tableModel->setModel(assignedModel);
}
needsModelSynchronization = false;
connectToModel();
}
@ -4748,6 +4754,11 @@ void QQuickTableViewPrivate::connectToModel()
} else {
QObjectPrivate::connect(model, &QQmlInstanceModel::modelUpdated, this, &QQuickTableViewPrivate::modelUpdated);
}
if (tableModel) {
QObject::connect(tableModel, &QQmlTableInstanceModel::modelChanged,
q, &QQuickTableView::modelChanged);
}
}
void QQuickTableViewPrivate::disconnectFromModel()
@ -4774,6 +4785,11 @@ void QQuickTableViewPrivate::disconnectFromModel()
} else {
QObjectPrivate::disconnect(model, &QQmlInstanceModel::modelUpdated, this, &QQuickTableViewPrivate::modelUpdated);
}
if (tableModel) {
QObject::disconnect(tableModel, &QQmlTableInstanceModel::modelChanged,
q, &QQuickTableView::modelChanged);
}
}
void QQuickTableViewPrivate::modelUpdated(const QQmlChangeSet &changeSet, bool reset)

View File

@ -427,6 +427,7 @@ public:
QItemSelectionModel::SelectionFlag selectionFlag = QItemSelectionModel::NoUpdate;
std::function<void(CallBackFlag)> selectableCallbackFunction;
bool inSelectionModelUpdate = false;
bool needsModelSynchronization = false;
int assignedPositionViewAtRowAfterRebuild = 0;
int assignedPositionViewAtColumnAfterRebuild = 0;

View File

@ -68,6 +68,19 @@ Item {
property int y: 12
}
function aAt0() : real {
switch (modelIndex) {
case Model.Singular:
case Model.List:
return model.get(0).a
case Model.Array:
return model[0].a
case Model.Object:
return model.a
}
return -1;
}
property int modelIndex: Model.None
property int delegateIndex: Delegate.None

View File

@ -8382,6 +8382,18 @@ void tst_QQuickTableView::delegateModelAccess()
? access != QQmlDelegateModel::ReadOnly
: access == QQmlDelegateModel::ReadWrite;
const bool writeShouldPropagate =
// If we've explicitly asked for the model to be written, it is
(access == QQmlDelegateModel::ReadWrite) ||
// If it's a QAIM or an object, it's implicitly written
(modelKind != Model::Kind::Array) ||
// When writing through the model object from a typed delegate,
// (like with DelegateModel).
(access == QQmlDelegateModel::Qt5ReadWrite && delegateKind == Delegate::Typed);
double expected = 11;
QCOMPARE(delegate->property("immediateX").toDouble(), expected);
@ -8394,6 +8406,10 @@ void tst_QQuickTableView::delegateModelAccess()
QCOMPARE(delegate->property("immediateX").toDouble(), expected);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
double aAt0 = -1;
QMetaObject::invokeMethod(tableView, "aAt0", Q_RETURN_ARG(double, aAt0));
QCOMPARE(aAt0, writeShouldPropagate ? expected : 11);
if (immediateWritable)
expected = 1;
@ -8404,6 +8420,10 @@ void tst_QQuickTableView::delegateModelAccess()
delegateKind == Delegate::Untyped ? expected : 1);
QCOMPARE(delegate->property("modelX").toDouble(), expected);
aAt0 = -1;
QMetaObject::invokeMethod(tableView, "aAt0", Q_RETURN_ARG(double, aAt0));
QCOMPARE(aAt0, writeShouldPropagate ? expected : 11);
}
void tst_QQuickTableView::checkVisualRowColumnAfterReorder()