Как обновить изменения в пользовательском QAbstractListModel типа SingleTon в QML ListView? - PullRequest
1 голос
/ 08 июля 2019

Я очень новичок в QML, поэтому мне не терпится узнать, как распространять изменения в пользовательском QAbstractListModel в QML 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 Ответ

1 голос
/ 08 июля 2019

Часть первая: интерфейс QAbstractListModel

Прежде всего, ваш HackNewsModel должен сигнализировать о добавлении строки.Добавьте следующее к вашему методу void 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, убедитесь, что вы также внедрили другие пары начала * и конца *.

См. Документы: https://doc.qt.io/qt-5/qabstractitemmodel.html#beginInsertRows

Часть вторая: синглтон 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();
    });

Примечание: установка владельца может быть необязательной

Это также означает, что вы не можете создать экземплярHackNewsModel как вы делаете в своем QML, если я прав, должно работать следующее:

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