需求理解
首先解释下标题中的‘不规则二维视图’,以便能够理解具体项目的使用场景。
二维视图: Qt中的
QAbstractListModel
为一个一维的抽象模型, 而QAbstractTableModel
为一个每行的列数(或者说每列的行数)都相同的规则的二维抽象模型,最后是QAbstractItemModel
,大部分与树形视图结合使用。
不规则:需求的界面是类似Windows的资源管理器,数据层上一共分三级,上一级与下一级都是一对多的关系。界面上也是分别对三级的数据显示:最左侧显示所有顶级内容,中间侧显示已选中顶级内容对应的下一级内容,最右侧显示已显示中间级内容对应的下一级内容。可以理解为对一个树形结构,只展开选中的那一项;
针对以上需求,做出的设计是:
- 第一级视图层用一个
ListView
显示,数据层为一个简单的基于QAbstractListModel
的类 - 第二级与第三级视图层用一个
ListView
显示,但这个View
的Delegate
中嵌套了一个GridView
,用于显示第三级的内容,数据层上对于两个级别的内容,都用一个基于于QAbstractListModel
的类来实现,不同之处在于第二级的数据层中提供返回它对应第三级的类的指针,以供QML视图层的GridView
来渲染。
如上图所示,最左侧为第一级,中间’A,B’为选中状态‘1’的第二级内容,两个时间块分别为A,B对应的第三级内容
代码设计
主要贴下第二级的Model类的关键代码:
typedef struct _subSchedule {
QString name;
int id;
StageModel* stageModel;
~_subSchedule() {
if( stageModel != nullptr ) {
delete stageModel;
stageModel = nullptr;
}
}
} SubScheduleStruct;
class SubScheduleModel : public QAbstractListModel
{
Q_OBJECT
public:
enum {
NameRole = Qt::UserRole + 1,
IdRole
};
SubScheduleModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
bool setData(const QModelIndex &index, const QVariant &value, int role);
virtual QHash<int, QByteArray> roleNames() const;
bool addSubSchedule(const QString name, const int id);
bool insertSubSchedule(const int index, const QString name, const int id);
void removeSubScheduleWithId(const int id);
bool moveSubSchedule(const int fromId, const int toId);
void editName(const QString name, const int id);
void addStage(const int index, QDateTime date, QString title, QString details, QString result, int status, int id);
Q_INVOKABLE StageModel *selectStageModel(const int index);
void clear();
SubScheduleStruct* selectSubSchedule(const int index);
int size() { return m_subScheduleData.size(); }
QString errorText() { return m_errorText; }
private:
int NameSearchSubSchedule(const QString name);
int IdSearchSubSchedule(const int id);
void clearErrorText() { m_errorText.clear(); }
void setErrorText(const QString error) { m_errorText = error; }
private:
QVector<SubScheduleStruct*> m_subScheduleData;
QString m_errorText;
};
下面是第二级视图层subScheduleDelegate
的关键代码:
Component {
id: subScheduleDelegate
Item {
···
GridView {
id:stageView
model: subScheduleModel.selectStageModel(index)
delegate: stageDelegate
}
Component {
id: stageDelegate
Item {
···
}
}
}
}
说明下第二级(SubScheduleModel类)与第三级(StageModel类)的关系:
SubScheduleStruct
内封装了构成一个完整SubSchedule
所需的数据,同时提供了一个返回第三级指针的变量StageModel* stageModel
Q_INVOKABLE StageModel *selectStageModel(const int index);
函数为提供给QMl获取第三级model的一个方法。第一级Model与第二级Model将会通过c++注册到qml中,使得qml能直接使用,而第三级的Model将通过第二级Model的一个实例方法(即selectStageModel
方法)来获取Model地址。- 对项目来看,第一级Model与第二级Model都只有一个实例,通过更新这两级的数据即可完成界面渲染,而第三级会根据第二级对应的关系而存在多个实例。
问题
这期间发现由于QMl中ListView
在渲染组件时,对没出现在界面的单个Item会销毁,但本次设计的Item内存在一个GridView
,同时这个View有对应一个第三级Model,在销毁的时候,QMl同时将第三级Model给销毁了,导致在下次重新访问或修改数据时会发生错误;
Qt也提供了解决方法:
QtQMl提供接口可以设置对象的所有权
QQmlEngine::setObjectOwnership(stageModel, QQmlEngine::CppOwnership);
QQmlEngine::CppOwnership
:对象由c++代码拥有,QML永远不会删除它。JavaScript destroy()方法不能用于这些对象。问题解决
详细代码地址:
(开发中)github
- 有用到数据库,需要详细表内容的可私信我
- 项目中有用到额外控件:lirios,也可以通过屏蔽这部分相关代码来运行;