QML中ListView使用cpp的Model

ListView的模型可以直接在qml上使用ListModel、XmlListModel 、ObjectModel等。
但总有时候需要用到c++的模型,比如说后台数据动态刷新等。

ListView使用c++的模型,必须要继承QAbstractItemModel或者其子类。
为了方便以后查看和理解,本文先做一些讲解,在文章最后面有完整的代码可以查看。

第一步 创新一个模型类

利用Qt创建一个c++的类,并继承QAbstractListModelQAbstractListModelQAbstractItemModel的子类。

class MyListViewModel : public QAbstractListModel
{
    Q_OBJECT
public:
    explicit MyListViewModel(QObject *parent = nullptr);
};

第二步 创建一个结构体

这个结构体,是用来表示ListView中的数据结构的,也可以创建一个类来使用,但我觉得结构体的代码更少,更美观(就是懒)。
定义好结构体后,再用一个QList放数据列表, ListView的数据就都在这个QList里头了。

private:
    struct Data{
        QString title_;
        QString content_;
        bool select_;
    };

    QList<Data> dataList_;

第三步 重写函数

我们在继承了QAbstractListModel后,有几个函数是必须要重写的,不然ListView可能会拿不到数据。
第一个:int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    这个是为了让ListView获取到数据的个数;
    因为我们的数据存放在QList中,所以获取QList的个数就好了。
第二个:virtual QHash<int, QByteArray> roleNames() const override;
    这个是为了给ListView获取数据时,能使用一个别名;
    利用这个别名,就能获取相对应的数据;
    这里需要定义一个enum来做配合。
第三个:QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    这个是ListView获取数据时调用的函数;
    数据是从QList中Data获取的;
    但到底是要获取title_,还是content_,需要跟函数roleNames打配合。
看代码:
MyListViewModel.h

public:
    // 这几个函数必须要重写
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    virtual QHash<int, QByteArray> roleNames() const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    
private:
    enum DataRoles{
        TitleRole = Qt::UserRole + 1,
        ContentRole,
        SelectRole,
    };

MyListViewModel.cpp

/** ListView需要获取数据的数量
 * @brief MyListViewModel::rowCount
 * @param parent
 * @return
 */
int MyListViewModel::rowCount(const QModelIndex &parent) const
{
    return dataList_.size();
}

/** ListView获取数据时,需要的别名
 * @brief MyListViewModel::roleNames
 * @return
 */
QHash<int, QByteArray> MyListViewModel::roleNames() const
{
    // 这样设置好后,ListView就可以在delegate中,
    // 直接使用title_、content_、select_来获取数据了。
    QHash<int, QByteArray> roles;
    roles[TitleRole] = "title_";
    roles[ContentRole] = "content_";
    roles[SelectRole] = "select_";
    return roles;
}

/** ListView获取数据,需要跟roleNames()打配合
 * @brief MyListViewModel::data
 * @param index
 * @param role
 * @return
 */
QVariant MyListViewModel::data(const QModelIndex &index, int role) const
{
    int row = index.row();
    if(row < 0 || row >= dataList_.count()) {
        return QVariant();
    }

    const Data &data = dataList_[row];
    switch (role) {
    case TitleRole:
        return data.title_;
    case ContentRole:
        return data.content_;
    case SelectRole:
        return data.select_;
    default:
        return QVariant();
    }
}

第四步 编写qml可调用函数

到这里的时候,模型已经基本完成了,只要QList中有数据,qml就可以显示出来。
但如果想要qml可以操作模型数据的话,还需要再另写函数。
这里就写一个添加和删除的代码。

Q_INVOKABLE void append(const QString &title, const QString &content, const bool &select);
Q_INVOKABLE void remove(int index);
void MyListViewModel::append(const QString &title, const QString &content, const bool &select)
{
    Data data;
    data.title_ = title;
    data.content_ = content;
    data.select_ = select;

    // 在增删改查数据时,都需要发送相对应的信号,这样qml才能及时刷新数据
    emit beginInsertRows(QModelIndex(), dataList_.size(), dataList_.size());
    dataList_.append(data);
    emit endInsertRows();
}

void MyListViewModel::remove(int index)
{
    if(index < 0 || index >= dataList_.count()) {
        return;
    }

    // 在增删改查数据时,都需要发送相对应的信号,这样qml才能及时刷新数据
    emit beginRemoveRows(QModelIndex(), index, index);
    dataList_.removeAt(index);
    emit endRemoveRows();
}

第五步 在main函数中注册模型

这里是最重要的一步,如果这一步没有做的话,模型函数写得再漂亮也没用!
这里把它注册名为ListViewModel

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "MyListViewModel.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;

    // 最重要的一步,是需要把c++写好的模型注册一下,不然代码再好qml也不能使用
    MyListViewModel listModel;
    engine.rootContext()->setContextProperty("ListViewModel",&listModel);

    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

第六步 写qml

现在就可以开始在qml中写ListView。

ListView {
	anchors.fill: parent
	model: ListViewModel

	delegate: Item {
		width: parent.width
		height: 40

		Text {
			id: titleText
			text: qsTr(title_)
			anchors.verticalCenter: parent.verticalCenter
			font.pixelSize: 18
			x: 20
		}

		Text {
			id: contentText
			text: qsTr(content_)
			anchors.verticalCenter: parent.verticalCenter
			font.pixelSize: 18
			x: 100
		}

		CheckBox {
			anchors.verticalCenter: parent.verticalCenter
			checked: select_
			x: 250
		}

		Button{
			id: deleteButton
			width: 50
			height: 20
			text: qsTr("删除")
			anchors.right: parent.right
			anchors.rightMargin: 20
			anchors.verticalCenter: parent.verticalCenter
			background: Rectangle{
				anchors.fill: parent
				color: "red"
			}
			onClicked: {
				ListViewModel.remove(index)
			}
		}
		
	}
}

另外再开一个定时器,每三秒就添加一个数据

property int titleCount: 0
    Timer{
        interval: 3000;
        running: true;
        repeat: true;
        onTriggered: {
            // CheckBox 就设置为,偶数时选中,奇数时不选中。
            ListViewModel.append(titleCount.toString(), "测试"+titleCount, titleCount%2==0 ? true : false);
            titleCount++;
        }
    }

第七步 运行看看效果

在这里插入图片描述

删除一个数据试试:
在这里插入图片描述

第八步 完整代码

MyListViewModel.h

#ifndef MYLISTVIEWMODEL_H
#define MYLISTVIEWMODEL_H

#include <QObject>
#include <QDebug>
#include <QAbstractListModel>

class MyListViewModel : public QAbstractListModel
{
    Q_OBJECT
public:
    explicit MyListViewModel(QObject *parent = nullptr);

    // 这几个函数必须要重写
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    virtual QHash<int, QByteArray> roleNames() const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

    // 然后再添加几个,可以操作model的函数
    Q_INVOKABLE void append(const QString &title, const QString &content, const bool &select);
    Q_INVOKABLE void remove(int index);


private:
    enum DataRoles{
        TitleRole = Qt::UserRole + 1,
        ContentRole,
        SelectRole,
    };

    struct Data{
        QString title_;
        QString content_;
        bool select_;
    };

    QList<Data> dataList_;
};

#endif // MYLISTVIEWMODEL_H

MyListViewModel.cpp

#include "MyListViewModel.h"


MyListViewModel::MyListViewModel(QObject *parent)
{

}

/** ListView需要获取数据的数量
 * @brief MyListViewModel::rowCount
 * @param parent
 * @return
 */
int MyListViewModel::rowCount(const QModelIndex &parent) const
{
    return dataList_.size();
}

/** ListView获取数据时,需要的别名
 * @brief MyListViewModel::roleNames
 * @return
 */
QHash<int, QByteArray> MyListViewModel::roleNames() const
{
    // 这样设置好后,ListView就可以在delegate中,
    // 直接使用title_、content_、select_来获取数据了。
    QHash<int, QByteArray> roles;
    roles[TitleRole] = "title_";
    roles[ContentRole] = "content_";
    roles[SelectRole] = "select_";
    return roles;
}

/** ListView获取数据,需要跟roleNames()打配合
 * @brief MyListViewModel::data
 * @param index
 * @param role
 * @return
 */
QVariant MyListViewModel::data(const QModelIndex &index, int role) const
{
    int row = index.row();
    if(row < 0 || row >= dataList_.count()) {
        return QVariant();
    }

    const Data &data = dataList_[row];
    switch (role) {
    case TitleRole:
        return data.title_;
    case ContentRole:
        return data.content_;
    case SelectRole:
        return data.select_;
    default:
        return QVariant();
    }
}

void MyListViewModel::append(const QString &title, const QString &content, const bool &select)
{
    Data data;
    data.title_ = title;
    data.content_ = content;
    data.select_ = select;

    // 在增删改查数据时,都需要发送相对应的信号,这样qml才能及时刷新数据
    emit beginInsertRows(QModelIndex(), dataList_.size(), dataList_.size());
    dataList_.append(data);
    emit endInsertRows();
}

void MyListViewModel::remove(int index)
{
    if(index < 0 || index >= dataList_.count()) {
        return;
    }

    // 在增删改查数据时,都需要发送相对应的信号,这样qml才能及时刷新数据
    emit beginRemoveRows(QModelIndex(), index, index);
    dataList_.removeAt(index);
    emit endRemoveRows();
}

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "MyListViewModel.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;

    // 最重要的一步,是需要把c++写好的模型注册一下,不然代码再好qml也不能使用
    MyListViewModel listModel;
    engine.rootContext()->setContextProperty("ListViewModel",&listModel);

    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

main.qml

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.5

Window {
    visible: true
    width: 440
    height: 480
    title: qsTr("Hello World")

    ListView {
        anchors.fill: parent
        model: ListViewModel

        delegate: Item {
            width: parent.width
            height: 40

            Text {
                id: titleText
                text: qsTr(title_)
                anchors.verticalCenter: parent.verticalCenter
                font.pixelSize: 18
                x: 20
            }

            Text {
                id: contentText
                text: qsTr(content_)
                anchors.verticalCenter: parent.verticalCenter
                font.pixelSize: 18
                x: 100
            }

            CheckBox {
                anchors.verticalCenter: parent.verticalCenter
                checked: select_
                x: 250
            }

            Button{
                id: deleteButton
                width: 50
                height: 20
                text: qsTr("删除")
                anchors.right: parent.right
                anchors.rightMargin: 20
                anchors.verticalCenter: parent.verticalCenter
                background: Rectangle{
                    anchors.fill: parent
                    color: "red"
                }
                onClicked: {
                    ListViewModel.remove(index)
                }
            }
        }
    }


    property int titleCount: 0
    Timer{
        interval: 3000;
        running: true;
        repeat: true;
        onTriggered: {
            // CheckBox 就设置为,偶数时选中,奇数时不选中。
            ListViewModel.append(titleCount.toString(), "测试"+titleCount, titleCount%2==0 ? true : false);
            titleCount++;
        }
    }
}

  • 14
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值