首页 > 解决方案 > 如何更新 QML ListView 上 SingleTon 类型自定义 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

.cp 文件。

#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 不显示更新的整体。如何启用它以显示更新的条目?

谢谢。

标签: qtqmlqabstractlistmodel

解决方案


第一部分:QAbstractListModel 接口

首先,您HackNewsModel需要表明已经添加了一行。将以下内容添加到您的 voidaddHackNews方法中

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* 对。

请参阅文档:https ://doc.qt.io/qt-5/qabstractitemmodel.html#beginInsertRows

第二部分:QML 单例

其次,您实现单例模式的方式对 QML 引擎没有任何意义。你需要告诉引擎这个类是单例的:

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

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

这也意味着您不能HackNewsModel像在 QML 中那样实例化,如果我是正确的,以下应该可以工作:

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

推荐阅读