(十)ListView使用C++model

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 // VIDEOLISTMODEL_H
#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就能通过,现在也没搞清楚原因。
效果如下:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要实现 QML 的 ListView 多级目录,您可以通过自定义 C++ model 来实现。下面是一个简单的示例: 首先,您需要创建一个 C++ 类来实现您的 model。这个类需要继承自 QAbstractListModel,并实现 rowCount()、data() 和 roleNames() 函数。 ```cpp #include <QAbstractListModel> #include <QList> class MyModel : public QAbstractListModel { Q_OBJECT public: struct ListItem { QString text; QList<ListItem> children; }; enum ListRoles { TextRole = Qt::UserRole + 1 }; MyModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash<int, QByteArray> roleNames() const override; private: QList<ListItem> m_data; }; ``` 在上述代码中,我们定义了一个 ListItem 结构体,用于存储每个列表项的数据。该结构体包含一个 text 属性和一个 children 属性,用于存储该项的子项。 接着,我们定义了一个 ListRoles 枚举,用于定义模型中的角色。我们还定义了一个 MyModel 类,它继承自 QAbstractListModel,并实现了 rowCount()、data() 和 roleNames() 函数。 接下来,我们需要在 C++ 类中实现这些函数。代码如下: ```cpp MyModel::MyModel(QObject *parent) : QAbstractListModel(parent) { // 初始化数据 ListItem fruit; fruit.text = "Fruits"; fruit.children.append({ "Apple" }); fruit.children.append({ "Banana" }); fruit.children.append({ "Orange" }); ListItem vegetable; vegetable.text = "Vegetables"; vegetable.children.append({ "Carrot" }); vegetable.children.append({ "Broccoli" }); vegetable.children.append({ "Lettuce" }); m_data.append(fruit); m_data.append(vegetable); } int MyModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return m_data[parent.row()].children.count(); return m_data.count(); } QVariant MyModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); const ListItem &item = m_data[index.row()]; if (role == TextRole) return item.text; return QVariant(); } QHash<int, QByteArray> MyModel::roleNames() const { QHash<int, QByteArray> roles; roles[TextRole] = "text"; return roles; } ``` 在上述代码中,我们在构造函数中初始化了我们的数据。我们实现了 rowCount() 函数,用于返回父项或子项的数量。我们还实现了 data() 函数,用于返回指定项的数据,并根据角色返回不同的数据。最后,我们实现了 roleNames() 函数,用于定义模型中的角色名称。 接下来,我们需要在 QML 文件中创建 ListView,并将其绑定到我们的自定义 model。代码如下: ```qml ListView { id: myListView model: myModel delegate: Item { width: parent.width height: 40 Text { text: model.text font.bold: model.children ? true : false } ListView { width: parent.width height: childrenListView.contentHeight model: model.children visible: model.children ? true : false delegate: Item { width: parent.width height: 40 Text { text: model.text } } id: childrenListView } } } ``` 在上述代码中,我们创建了一个 ListView,其中的 delegate 是一个 Item。在 Item 中,我们创建了一个 Text 来显示该项的 text 属性,并根据 children 属性的有无设置字体是否加粗。 接着,我们创建了另外一个 ListView 作为子项列表,并将其绑定到该项的 children 属性。我们也在该 ListView 的 delegate 中创建了一个 Text 来显示子项的 text 属性。 通过这种方式,您可以创建一个多级目录的 ListView,并通过自定义 C++ model 来实现。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

vegetablesssss

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值