当多个 View 展示同一个数据集中的部分数据项时,其中一个 View 进行了增删改操作,也需要同步到其他的 View。有两种比较简单的解决方案:
方案一:如果都是同一种 View(如都是 ListView),那可以共用一个 Model 实例,通过 Proxy 来辅助完成过滤和选择等操作,实践起来也比较简单,几乎不用自己做额外的同步。
方案二:如果数据项需要同时在 ListView、TreeView 等不同的场景使用,那单个 Model 实例的方式实现起来就很麻烦了,这时可以让多个 Model 持有同一组数据项指针(建议用智能指针),更新之后通知所有关联的 View 进行同步刷新。
本文 Demo 采用方案二共享数据项指针的方式,UI 使用 QML 实现。通过 Model 的 data 接口访问数据时,是可以返回指针的,然后 Delegate 通过指针访问数据项的属性。但是 QML 中没法识别智能指针(至少 Qt5 下测试识别不了),只能从智能指针取出原始指针后返回给 QML。但是 QML Delegate 访问原始指针的属性还有问题,就是初始化或者增删时,可能报属性未定义或者是不存在,所以还是先老老实实从 data 接口返回数据项的属性值,而不是返回指针,这样排查问题也容易。
除了数据项指针,还需要一个对象(DataSource)来关联各个 Model/View,这样在一个 Model/View 中的增删改操作,就能同步到其他 Model/View 中。修改时调用 DataSource 接口修改,同时触发更新信号,关联 Model 收到信号后进行同步。考虑到一般编辑接口是需要 Model 的行列号来定位当前项的,QML 中可以通过 Model 来间接调用 DataSource 的增删改,而不是直接操作 DataSource 的增删改。
DataSource 接口定义如下:
#pragma once
#include <QObject>
#include <QSharedPointer>
struct DataInfo
{
QString key;
int value = 0;
};
class DataSource : public QObject
{
Q_OBJECT
private:
explicit DataSource(QObject *parent = nullptr);
public:
~DataSource();
static DataSource* getInstance();
//增删改接口
void deleteItem(QSharedPointer<DataInfo> item);
void appendItem(const QString &key, int value);
void appendItem(QSharedPointer<DataInfo> item);
void updateItem(QSharedPointer<DataInfo> item);
//根据value范围查询数据
QList<QSharedPointer<DataInfo>> searchItemByValue(int min, int max);
signals:
//增删改通知其他view更新
void itemDeleteNotify(QSharedPointer<DataInfo> item);
void itemAppendNotify(QSharedPointer<DataInfo> item);
void itemUpdateNotify(QSharedPointer<DataInfo> item);
private:
//存储着所有的数据
QList<QSharedPointer<DataInfo>> dataList;
};
View 中操作 Model 的增删改接口:
Button {
text: "update"
onClicked: {
//update更新所有view中的该item
data_model.updateItemByRow(model.index, "key", 123)
}
}
Button {
text: "delete"
onClicked: {
//删除item
data_model.deleteItemByRow(model.index)
}
}
void DataModel::deleteItemByRow(int row)
{
if(row < 0 || row >= dataList.size())
return;
DataSource::getInstance()->deleteItem(dataList.at(row));
}
void DataModel::updateItemByRow(int row, const QString &key, int value)
{
if(row < 0 || row >= dataList.size())
return;
auto item = dataList.at(row);
item->key = key;
item->value = value;
DataSource::getInstance()->updateItem(item);
}
DataSource 信号通知关联 Model 刷新:
DataModel::DataModel(QObject *parent)
: QAbstractListModel(parent)
{
auto source = DataSource::getInstance();
connect(source, &DataSource::itemAppendNotify,
this, [this](QSharedPointer<DataInfo> item){
//符合条件,插入
if(item->value >= searchMin && item->value <= searchMax){
beginInsertRows(QModelIndex(), rowCount(), rowCount());
dataList.append(item);
endInsertRows();
}
});
connect(source, &DataSource::itemDeleteNotify,
this, [this](QSharedPointer<DataInfo> item){
//存在则移除
int row = dataList.indexOf(item);
if(row >= 0){
beginRemoveRows(QModelIndex(), row, row);
dataList.removeAt(row);
endRemoveRows();
}
});
connect(source, &DataSource::itemUpdateNotify,
this, [this](QSharedPointer<DataInfo> item){
int row = dataList.indexOf(item);
if(row >= 0){
//已存在,判断是否需要移除
if(item->value >= searchMin && item->value <= searchMax){
emit dataChanged(index(row, 0), index(row, 0));
}else{
beginRemoveRows(QModelIndex(), row, row);
dataList.removeAt(row);
endRemoveRows();
}
}else if(item->value >= searchMin && item->value <= searchMax){
//不存在,判断是否需要重新查询
searchItemByValue(searchMin, searchMax);
}
});
}
通知关联 View 刷新的时候,每个 Model 都需要去查询是否存在这个数据项,以及他在列表中的位置,但这种查询都是线性遍历一次,应该不会有太大的损耗,即使数据量很大。
实现效果:
完整代码:https://github.com/gongjianbo/MyTestCode/tree/master/Qml/TestQml_20220709_CommonData