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 dataList_;
第三步 重写函数
我们在继承了QAbstractListModel后,有几个函数是必须要重写的,不然ListView可能会拿不到数据。
第一个:int rowCount(const QModelIndex &parent = QModelIndex()) const override;
这个是为了让ListView获取到数据的个数;
因为我们的数据存放在QList中,所以获取QList的个数就好了。
第二个:virtual QHash
这个是为了给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 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
int MyListViewModel::rowCount(const QModelIndex &parent) const
{
return dataList_.size();
}
QHash MyListViewModel::roleNames() const
{
// 这样设置好后,ListView就可以在delegate中,
// 直接使用title_、content_、select_来获取数据了。
QHash roles;
roles[TitleRole] = "title_";
roles[ContentRole] = "content_";
roles[SelectRole] = "select_";
return roles;
}
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第六步 写qml#include #include #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中写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#include #include class MyListViewModel : public QAbstractListModel { Q_OBJECT public: explicit MyListViewModel(QObject *parent = nullptr); // 这几个函数必须要重写 int rowCount(const QModelIndex &parent = QModelIndex()) const override; virtual QHash 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 dataList_; }; #endif // MYLISTVIEWMODEL_H
MyListViewModel.cpp
#include "MyListViewModel.h"
MyListViewModel::MyListViewModel(QObject *parent)
{
}
int MyListViewModel::rowCount(const QModelIndex &parent) const
{
return dataList_.size();
}
QHash MyListViewModel::roleNames() const
{
// 这样设置好后,ListView就可以在delegate中,
// 直接使用title_、content_、select_来获取数据了。
QHash roles;
roles[TitleRole] = "title_";
roles[ContentRole] = "content_";
roles[SelectRole] = "select_";
return roles;
}
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#include #include #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++;
}
}
}



