ListView的模型可以直接在qml上使用ListModel、XmlListModel 、ObjectModel等。
但总有时候需要用到c++的模型,比如说后台数据动态刷新等。
ListView使用c++的模型,必须要继承QAbstractItemModel或者其子类。
为了方便以后查看和理解,本文先做一些讲解,在文章最后面有完整的代码可以查看。
第一步 创新一个模型类
利用Qt创建一个c++的类,并继承QAbstractListModel,QAbstractListModel是QAbstractItemModel的子类。
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++;
}
}
}