QML多级Model与Delegates嵌套设计

需求理解

首先解释下标题中的‘不规则二维视图’,以便能够理解具体项目的使用场景。

二维视图: Qt中的QAbstractListModel为一个一维的抽象模型, 而QAbstractTableModel为一个每行的列数(或者说每列的行数)都相同的规则的二维抽象模型,最后是QAbstractItemModel,大部分与树形视图结合使用。

不规则:需求的界面是类似Windows的资源管理器,数据层上一共分三级,上一级与下一级都是一对多的关系。界面上也是分别对三级的数据显示:最左侧显示所有顶级内容,中间侧显示已选中顶级内容对应的下一级内容,最右侧显示已显示中间级内容对应的下一级内容。可以理解为对一个树形结构,只展开选中的那一项;

针对以上需求,做出的设计是:

  • 第一级视图层用一个ListView显示,数据层为一个简单的基于QAbstractListModel的类
  • 第二级与第三级视图层用一个ListView显示,但这个ViewDelegate中嵌套了一个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,也可以通过屏蔽这部分相关代码来运行;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值