如何在QML ListView上更新SingleTon类型自定义QAbstractListModel中的更改?

时间:2019-07-08 07:17:42

标签: qt qml qabstractlistmodel

我对QML还是很陌生,所以在如何将自定义QAbstractListModelQML List View中传播更改方面遇到了困难。

我有以下HackNewsModel

头文件

#ifndef HACKNEWSMODEL_H
#define HACKNEWSMODEL_H

#include "Singleton.hpp"
#include <QAbstractListModel>
#include <QJsonObject>
#include <QDateTime>

struct HackNews
{
    QString m_id;
    bool m_deleted;
    QString m_type;
    QString m_by;
    QDateTime m_time;
    QString m_text;
    bool m_dead;
    QString m_parentId;
    QString m_pollId;
    QStringList m_kidsIdList;
    QString m_url;
    QString m_score;
    QString m_title;
    QStringList m_partsIdList;
    QString m_descendantCount;
};

class HackNewsModel : public QAbstractListModel, public Singleton<HackNewsModel>
{
    Q_OBJECT

public:
    void addHackNews(QJsonObject &hackNews);
    enum Roles {
        IdRole = Qt::UserRole + 1
    };

    QHash<int, QByteArray> roleNames() const override;

    int rowCount(const QModelIndex& parent = QModelIndex()) const override;

    QVariant data(const QModelIndex& index, int role/* = Qt::DisplayRole*/) const override;

    friend class Singleton<HackNewsModel>;
    explicit HackNewsModel(QObject * parent = nullptr);
    ~HackNewsModel() override;

private:
    QList<HackNews> m_hackNewsList;
    QHash<int, QByteArray> m_roles;

};

#endif // HACKNEWSMODEL_H

Cpp文件。

#include "HackNewsModel.h"
#include <QJsonArray>
#include <QDebug>

HackNewsModel::HackNewsModel(QObject *parent) : QAbstractListModel(parent)
{
    m_roles[0] = "id";

    QString id = "Demo id";
    bool deleted = false;
    QString type;
    QString by;
    QDateTime time;
    QString text;
    bool dead = false;
    QString parentId;
    QString pollId;
    QStringList kidsIdList;
    QString url;
    QString score;
    QString title;
    QStringList partsIdList;
    QString descendantCount;
    m_hackNewsList.append(HackNews{id+"1", deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});
    m_hackNewsList.append(HackNews{id+"2", deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});
    m_hackNewsList.append(HackNews{id+"3", deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});
    m_hackNewsList.append(HackNews{id+"4", deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});
    m_hackNewsList.append(HackNews{id+"5", deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});
}

HackNewsModel::~HackNewsModel()
{

}

void HackNewsModel::addHackNews(QJsonObject &hackNews)
{
    QString id = "Demo id";
    bool deleted = false;
    QString type;
    QString by;
    QDateTime time;
    QString text;
    bool dead = false;
    QString parentId;
    QString pollId;
    QStringList kidsIdList;
    QString url;
    QString score;
    QString title;
    QStringList partsIdList;
    QString descendantCount;

    if(hackNews.contains("id"))
    {
        id = hackNews["id"].toString();
    }

    if(hackNews.contains("deleted"))
    {
        deleted = hackNews["deleted"].toBool();
    }

    if(hackNews.contains("type"))
    {
        type = hackNews["type"].toString();
    }
    if(hackNews.contains("by"))
    {
        by = hackNews["by"].toString();
    }

    if(hackNews.contains("time"))
    {
        time = QDateTime::fromTime_t(static_cast<unsigned int>(hackNews["time"].toInt()));
    }

    if(hackNews.contains("text"))
    {
        text = hackNews["text"].toString();
    }

    if(hackNews.contains("dead"))
    {
        dead = hackNews["dead"].toBool();
    }

    if(hackNews.contains("parent"))
    {
        parentId = hackNews["parent"].toString();
    }

    if(hackNews.contains("poll"))
    {
        pollId = hackNews["poll"].toString();
    }

    if(hackNews.contains("kids"))
    {
        foreach (QVariant value, hackNews["kids"].toArray().toVariantList()) {
            kidsIdList.append(value.toString());
        }
    }

    if(hackNews.contains("url"))
    {
        url = hackNews["url"].toString();
    }

    if(hackNews.contains("title"))
    {
        title = hackNews["title"].toString();
    }

    if(hackNews.contains("parts"))
    {
        foreach (QVariant value, hackNews["parts"].toArray().toVariantList()) {
            partsIdList.append(value.toString());
        }
    }

    if(hackNews.contains("descendents"))
    {
        descendantCount = hackNews["descendents"].toString();
    }

    m_hackNewsList.append(HackNews{id, deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});
}

QHash<int, QByteArray> HackNewsModel::roleNames() const
{
    return m_roles;
}

int HackNewsModel::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return 0;
    return m_hackNewsList.size();
}


QVariant HackNewsModel::data(const QModelIndex &index, int /*role*/) const
{
    //    if (!hasIndex(index.row(), index.column(), index.parent()))
    if(!index.isValid())
        return QVariant();

    const HackNews &news = m_hackNewsList.at(index.row());

    //    if(role == IdRole){
    //        qDebug() << "Seeking id";
    return news.m_id;
    //    }

    //    return QVariant();
}

但是,此数据模型是通过NetworkRequestMaker更新的,该模型向网络发出了一些请求并更新了模型。

NetworkRequestMaker的头文件。

#ifndef NETWORKREQUESTMAKER_H
#define NETWORKREQUESTMAKER_H

#include <QObject>
#include <QNetworkAccessManager>

class QNetworkReply;
class NetworkRequestMaker : public QObject
{
    Q_OBJECT

public:
    explicit NetworkRequestMaker(QObject *parent = nullptr);
    void startRequest(const QUrl &requestedUrl);
    void httpReadyRead();
    void httpFinished();

private:
    QUrl url;
    QNetworkAccessManager m_qnam;
    QNetworkReply *m_reply;
};

#endif // NETWORKREQUESTMAKER_H

Cpp文件。

#include "NetworkRequestMaker.h"
#include <QNetworkReply>
#include <QDebug>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QDateTime>
#include "HackNewsModel.h"

NetworkRequestMaker::NetworkRequestMaker(QObject *parent)
    : QObject(parent),
      m_reply(nullptr)
{
    startRequest(QUrl("https://hacker-news.firebaseio.com/v0/item/8863.json?print=pretty"));
    startRequest(QUrl("https://hacker-news.firebaseio.com/v0/item/2921983.json?print=pretty"));
    startRequest(QUrl("https://hacker-news.firebaseio.com/v0/item/121003.json?print=pretty"));
    startRequest(QUrl("https://hacker-news.firebaseio.com/v0/item/192327.json?print=pretty"));
    startRequest(QUrl("https://hacker-news.firebaseio.com/v0/item/126809.json?print=pretty"));
    startRequest(QUrl("https://hacker-news.firebaseio.com/v0/item/160705.json?print=pretty"));
}

void NetworkRequestMaker::startRequest(const QUrl &requestedUrl)
{
    url = requestedUrl;
    m_reply = m_qnam.get(QNetworkRequest(url));
    connect(m_reply, &QNetworkReply::finished, this, &NetworkRequestMaker::httpFinished);
    connect(m_reply, &QIODevice::readyRead, this, &NetworkRequestMaker::httpReadyRead);
}

void NetworkRequestMaker::httpReadyRead()
{
    QString strReply = QString(m_reply->readAll());
    QJsonDocument jsonResponse = QJsonDocument::fromJson(strReply.toUtf8());
    QJsonObject jsonObj = jsonResponse.object();
    HackNewsModel::getInstance().addHackNews(jsonObj);
}

void NetworkRequestMaker::httpFinished()
{
    if (m_reply->error()) {
        qDebug()<<tr("Download failed:\n%1.").arg(m_reply->errorString());
    }
}

单例类如下。

#ifndef SINGLETON_HPP
#define SINGLETON_HPP

template <typename T>
class Singleton
{
public:

    /*!*************************************************************************
    \brief      Constructs the singleton (if necessary) and returns the pointer.
    ****************************************************************************/
    static T& getInstance()
    {
        static T _singleton; //!< Unique instance of class T
        return _singleton;
    }

protected:

    /*!*************************************************************************
    \brief      Constructor.
    \note       protected to avoid misuses.
    ****************************************************************************/
    Singleton() {}

    /*!*************************************************************************
    \brief      Destructor.
    \note       protected to avoid misuses.
    ****************************************************************************/
    virtual ~Singleton() {}

    /*!*************************************************************************
    \brief      Copy constructor.
    \note       protected to avoid misuses.
    ****************************************************************************/
    Singleton(const Singleton&);

    /*!*************************************************************************
    \brief      Assignment operator.
    \note       protected to avoid misuses.
    ****************************************************************************/
    Singleton& operator=(const Singleton&);
};

#endif // SINGLETON_HPP

主要cpp文件如下。

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "NetworkRequestMaker.h"
#include "HackNewsModel.h"

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

    NetworkRequestMaker testRequestMaker;

    qmlRegisterType<HackNewsModel>("Hacknews", 1, 0, "HackNewsModel");

    QQmlApplicationEngine engine;
    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文件如下。

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import Hacknews 1.0

Frame {
    width: 640
    height: 480
    ListView {
        id: listView
        anchors.fill: parent
        model: HackNewsModel {}

        delegate: Text {
            text: model.id
        }
    }
}

尽管模型是单例的,但qmnl listview不会显示更新的整体。如何启用它以显示更新的条目?

谢谢。

1 个答案:

答案 0 :(得分:1)

第一部分:QAbstractListModel接口

首先,您的HackNewsModel需要表示已添加一行。将以下内容添加到您的无效addHackNews方法中

void HackNewsModel::addHackNews(QJsonObject &hackNews)
{
    ...

    beginInsertRows(QModelIndex(), rowCount(), rowCount());        
    m_hackNewsList.append(HackNews{id, deleted, type, by, time, text, dead, parentId, pollId, kidsIdList, url, score, title, partsIdList, descendantCount});
    endInsertRows();
}

beginInsertRows方法要求满足以下条件:

  • 父模型索引。如果您使用的索引无效,则列表是固定的。
  • 已添加的开始索引。将其设置为列表的大小(比上一项的从零开始的索引大1)
  • 端已添加。将其设置为开始索引,因为您仅添加了一项。

如果您要为HackNewsModel添加更多功能,请确保也实现其他的begin *和end *对。

查看文档:{​​{3}}

第二部分:QML单例

第二,实现Singleton模式的方式对QML Engine毫无意义。您需要告诉引擎该类是单例:

qmlRegisterSingletonType<HackNewsModel>("HackNews", 1, 0, "HackNewsModel",
    [](QQmlEngine *eng, QJSEngine *js) -> QObject *
    {
        eng->setObjectOwnership(&HackNewsModel::getInstance(),
                               QQmlEngine::ObjectOwnership::CppOwnership);
        return &HackNewsModel::getInstance();
    });

注意:设置所有权可能不是强制性的

这也意味着您无法像在QML中那样实例化HackNewsModel,如果我正确的话,以下方法应该起作用:

ListView {
    id: listView
    model: HackNewsModel
    ...
}