C++实现QML可用的Model
ListView可以使用C++语言定义的Model,可以从QAbstractItemModel或者QAbstractListModel继承来实现自己的Model类。最简单的实现,只需要重写rowCount()、data()、rowNames()这三个方法。rowCount()返回Model中的数据条目个数,data(const QModelIndex& index, int role)用来获取某一行、某个角色对应的数据,QModelIndex代表Model中所存放数据的索引,它通过行列位置来唯一确定一个数据,一个数据条目可能有多个role,比如用于显示的DisplayRole,用于鼠标悬停提示的ToolTipRole,还可以用户自定义的role。 对于Qt Quick中的ListView,Delegate通过role-name来访问Model中的数据,当我们在QML中写下Text{text:desc;}这样的Delegate时,意味着将名字为desc的role对应的数据赋值给Text对象的text属性。如何从desc找到实际的数据?这就要依赖rowNames()函数,QHash<int,QBythArray> roleNames() const 返回一个哈希表,将role与rolename关联起来,当QML中提供role-name时,那么就可以反查到role,进而以查到的role来调用data()方法,就可以获取到实际的数据。
#ifndef VIDEOLISTMODEL_H
#define VIDEOLISTMODEL_H
#include<QAbstractListModel>
class VideoListModelPrivate;
class VideoListModel:public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QString source READ source WRITE setSource)
public:
VideoListModel(QObject* parent = nullptr);
~VideoListModel();
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
QString source() const;
void setSource(const QString& filePath);
Q_INVOKABLE QString errorString() const;
Q_INVOKABLE bool hasError() const;
Q_INVOKABLE void reload();
Q_INVOKABLE void remove(int index);
private:
VideoListModelPrivate *m_dptr;
};
#endif
#include "videolistmodel.h"
#include<QXmlStreamReader>
#include<QVector>
#include<QFile>
#include<QDebug>
typedef QVector<QString> VideoData;
class VideoListModelPrivate
{
public:
VideoListModelPrivate():m_bError(false)
{
int role = Qt::UserRole;
m_roleNames.insert(role++, "name");
m_roleNames.insert(role++, "date");
m_roleNames.insert(role++, "director_tag");
m_roleNames.insert(role++, "director");
m_roleNames.insert(role++, "actor_tag");
m_roleNames.insert(role++, "actor");
}
~VideoListModelPrivate()
{
clear();
}
void load()
{
QXmlStreamReader reader;
QFile file(m_strXmlFile);
if(!file.exists())
{
m_bError = true;
m_strError = "File Not Found!";
return;
}
if(!file.open(QFile::ReadOnly))
{
m_bError = true;
m_strError = file.errorString();
return;
}
reader.setDevice(&file);
QStringRef elementName;
VideoData* video;
while(!reader.atEnd())
{
reader.readNext();
if(reader.isStartElement())
{
elementName = reader.name();
if(elementName == "video")
{
video = new VideoData();
QXmlStreamAttributes attrs = reader.attributes();
video->append(attrs.value("name").toString());
video->append(attrs.value("date").toString());
}
else if(elementName == "attr")
{
video->append(reader.attributes().value("tag").toString());
video->append(reader.readElementText());
}
}
else if(reader.isEndElement())
{
elementName = reader.name();
if(elementName == "video")
{
m_videos.append(video);
video = 0;
}
}
}
file.close();
if(reader.hasError())
{
m_bError = true;
m_strError = reader.errorString();
}
}
void reset()
{
m_bError = false;
m_strError.clear();
clear();
}
void clear()
{
int count = m_videos.size();
if(count > 0)
{
for(int i = 0; i< count; i++)
{
delete m_videos.at(i);
}
m_videos.clear();
}
}
QString m_strXmlFile;
QString m_strError;
bool m_bError;
QHash<int, QByteArray> m_roleNames;
QVector<VideoData*> m_videos;
};
VideoListModel::VideoListModel(QObject* parent):QAbstractListModel(parent),m_dptr(new VideoListModelPrivate)
{
}
VideoListModel::~VideoListModel()
{
if(m_dptr!=nullptr)
{
delete m_dptr;
m_dptr = nullptr;
}
}
int VideoListModel::rowCount(const QModelIndex &parent) const
{
return m_dptr->m_videos.size();
}
QVariant VideoListModel::data(const QModelIndex &index, int role) const
{
VideoData* d = m_dptr->m_videos[index.row()];
return d->at(role-Qt::UserRole);
}
QHash<int, QByteArray> VideoListModel::roleNames() const
{
return m_dptr->m_roleNames;
}
QString VideoListModel::source() const
{
return m_dptr->m_strXmlFile;
}
void VideoListModel::setSource(const QString &filePath)
{
m_dptr->m_strXmlFile = filePath;
reload();
if(m_dptr->m_bError)
{
qDebug() << "VideoListModel,error - " << m_dptr->m_strError;
}
}
QString VideoListModel::errorString() const
{
return m_dptr->m_strError;
}
bool VideoListModel::hasError() const
{
return m_dptr->m_bError;
}
void VideoListModel::reload()
{
beginResetModel();
m_dptr->reset();
m_dptr->load();
endResetModel();
}
void VideoListModel::remove(int index)
{
beginRemoveRows(QModelIndex(), index, index);
delete m_dptr->m_videos.takeAt(index);
endRemoveRows();
}
主要是重写rowCount,data,roleNmaes这三个方法,rowCount表示ListView有多少行数据,data表示获取每一行每一列的数据,roleNames表示ListView中的role-name。
将model导出到QML中
main.cpp代码如下:不清楚可以参考C++与QML混合编程—在QML中使用C++对象
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml/QtQml>
#include "videolistmodel.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<VideoListModel>("an.qt.CModel",1,0,"VideoListModel");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
return app.exec();
}
main.qml代码如下:
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
import an.qt.CModel 1.0
Window {
visible: true;
width: 360;
height: 400;
color: "#EEEEEE";
Component{
id: videoDelegate;
Item{
id: wrapper;
width: parent.width;
height: 120;
MouseArea{
anchors.fill: parent;
onClicked: wrapper.ListView.view.currentIndex = index;
}
Image{
id: poster;
anchors.left: parent.left;
anchors.top: parent.top;
source: img;
width: 80;
height: 120;
fillMode: Image.PreserveAspectFit;
}
ColumnLayout{
anchors.left: poster.right;
anchors.leftMargin: 4;
anchors.right: wrapper.right;
anchors.top: poster.top;
height: parent.height;
spacing: 2;
Text{
Layout.fillWidth: true;
text: "<b>" + name +"<b>";
color: wrapper.ListView.isCurrentItem ? "blue" : "black";
font.pixelSize: 18;
elide: Text.ElideRight;
}
Text{
text: date;
Layout.fillWidth: true;
color: wrapper.ListView.isCurrentItem ? "blue" : "black";
font.pixelSize: 18;
elide: Text.ElideRight;
}
Text{
text: director_tag+": <font color=\"#0000aa\">" + director + "</font>";
Layout.fillWidth: true;
color: wrapper.ListView.isCurrentItem ? "blue" : "black";
font.pixelSize: 18;
elide: Text.ElideRight;
}
Text {
text: actor_tag+": <font color=\"#0000aa\">" + actor + "</font>";
Layout.fillWidth: true;
color: wrapper.ListView.isCurrentItem ? "blue" : "black";
font.pixelSize: 18;
elide: Text.ElideRight;
}
}
}
}
ListView{
id: listView;
anchors.fill: parent;
spacing: 4;
delegate: videoDelegate;
model: VideoListModel{
source: "videos.xml";
}
focus: true;
highlight: Rectangle{
width: parent.width;
color: "lightblue";
}
}
}
video.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<videos>
<video name="冰雪奇缘" date="2014-11-19">
<attr tag="导演">詹妮弗李</attr>
<attr tag="演员">伊迪娜</attr>
</video>
<video name="功夫" date="2004-12-23">
<attr tag="导演">周星驰</attr>
<attr tag="演员">周星驰</attr>
</video>
<video name="小时代" date="2013-6-27">
<attr tag="导演">郭敬明</attr>
<attr tag="演员">杨幂</attr>
</video>
<video name="倩女幽魂" date="1987-07-18">
<attr tag="导演">程小东</attr>
<attr tag="演员">张国荣</attr>
</video>
</videos>
其实一直都有一个比较奇怪的问题,使用qt总是无法通过编译,而使用vs2015就能通过,现在也没搞清楚原因。 效果如下:
|