【问题标题】:Displaying Json data with qml ListView使用 qml ListView 显示 Json 数据
【发布时间】:2018-03-17 20:45:11
【问题描述】:

我目前正在开发一个ticker客户端,它从Web api轮询数据,将其传递给ListModel,然后用于在qml ListView中显示数据。

class TickerClient : public QObject
{
    Q_OBJECT

public:
    explicit TickerClient(QObject *parent = nullptr);

    QList<QVariantMap> items() const;

protected:
    void registerErrorHandlers(QNetworkReply *reply);

signals:
    void statusChanged(QNetworkAccessManager::NetworkAccessibility acc);
    void dataChanged();

    void preItemRefresh();
    void postItemRefresh();

public slots:
    void fetch(int start = 0, int limit = 100);

protected slots:
    void onReceive(QNetworkReply *reply);

protected:
    QSharedPointer<QNetworkAccessManager> mNetMan;
    QList<QVariantMap> mItems;
};

class TickerModel : public QAbstractListModel
{
    Q_OBJECT
    Q_PROPERTY(TickerClient *client READ client WRITE setClient)

public:
    explicit TickerModel(QObject *parent = nullptr);

    enum {
        IdRole = Qt::UserRole + 1,
        NameRole,
        SymbolRole,
        ...
    };

    // Basic functionality:
    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;

    TickerClient *client() const;
    void setClient(TickerClient *client);

private:
    TickerClient *mClient;
};

ticker 客户端不仅获取,还处理获取的数据,并将其作为 QVariantMaps 列表暴露给周围的 ListModel。

void TickerClient::onReceive(QNetworkReply *reply)
{
    if (!reply)
        return;

    if(reply->error()) {
        qCritical() << "Error: "
                    << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString();
        return;
    }

    //  Read all data as json document.
    QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());

    if(!jsonDoc.isArray()) {
        qCritical() << "Error: Expected array";
        return;
    }

    emit preItemRefresh();

    mItems.clear();

    QJsonArray currencies = jsonDoc.array();
    for(int i = 0; i < currencies.count(); i++) {
        QJsonObject currency = currencies[i].toObject();
        mItems.append(currency.toVariantMap());
    }

    emit postItemRefresh();

    reply->deleteLater();
}

TickerClient 和 TickerModel 都暴露给 qml:

qmlRegisterType<TickerModel>("Ticker", 1, 0, "TickerModel");
qmlRegisterUncreatableType<TickerClient>("Ticker", 1, 0, "TickerClient",
        QStringLiteral("MarketCapProvider should not be created in QML"));

TickerClient tickerClient;

QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("tickerClient", &tickerClient);

然后将暴露的客户端交给模型并每 5 秒刷新一次:

ListView {
    id: page

    Timer {
        interval: 5000
        running: true
        repeat: true
        triggeredOnStart: true
        onTriggered: {
            var pos = scrollBar.position
            tickerClient.fetch()
            scrollBar.position = pos
        }
    }

    ScrollBar.vertical: ScrollBar {
        id: scrollBar
    }

    model: TickerModel {
        client: tickerClient
    }

    delegate: RowLayout {
        width: parent.width
        spacing: 10

        Label {
            text: "#" + model.rank
            padding: 5
        }

        Label {
            text: model.name
            padding: 5
        }
    }
}

但是,由于返回了错误的数据,数据的获取似乎并不顺利。所以如果我要求model.name,我可能会以model.rank 结束。以下代码用于获取给定一对indexrole 的条目。

QVariant TickerModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || !mClient)
        return QVariant();

    auto it = roleNames().find(role);
    if(it != roleNames().end())
        return mClient->items().at(index.row())[it.value()];
    else
        qDebug() << "Didn't match a role";

    return QVariant();
}

QHash<int, QByteArray> TickerModel::roleNames() const
{
    static const QHash<int, QByteArray> names{
        { IdRole, "id" },
        { NameRole, "name" },
        ...
    };

    return names;
}

TickerClient *TickerModel::client() const
{
    return mClient;
}

void TickerModel::setClient(TickerClient *client)
{
    beginResetModel();

    if(mClient) {
        mClient->disconnect(this);
    }

    mClient = client;
    if(mClient) {
        connect(mClient, &TickerClient::preItemRefresh, this, [=]() {
            beginResetModel();
        });

        connect(mClient, &TickerClient::postItemRefresh, this, [=]() {
            endResetModel();
        });
    }

    endResetModel();
}

什么,我做错了吗?如何为更复杂的 Json 对象扩展此解决方案?

【问题讨论】:

  • 学习 C++ 似乎是一种巨大的浪费。由于 JS 原生支持 JSON,您可以使用简单的 JS 数组作为列表视图的模型...
  • 我没有看到在 qml Timer tickerClient.fetch() 中调用的公共插槽 fetch() 的任何实现
  • @MohammadKanan 我没有将它添加到我的问题中,因为理解问题并不重要。如您所见,调用fetch()返回的数据在onReceive()中处理,它从http响应中提取json数据并将其推送到QVariantMaps列表中。
  • @dtech 有几个原因我认为(不知道)在这里使用 JS 不是一个好主意。我对 JS 不熟悉,也没有看到并行处理大量 http 请求的有效方法。此外,我不确定如何处理 Javascript 中丢失或更改模型实体等错误。那么,为什么我要使用 qml 而不是 Qt Widgets?我喜欢现代 UI 元素,例如 QDrawerQStackView,它们不能作为小部件使用。我的总体印象是 Widget UI 元素在外观和感觉上有些过时。但是,我愿意接受替代方案。
  • 好吧,你已经在使用 QML 来做 GUI,你不妨好好利用它。 C++ 代码所做的一切在 QML 中都是可能的并且更容易做到,您可以发出请求,解析返回的 JSON 并将结果用作模型。 JS 原生支持所有这些东西。在这种特殊情况下,使用 C++ 并没有为您带来任何好处,当您的瓶颈是 http 时,不会有明显的性能提升,只会增加复杂性并降低开发速度,因为您必须为每一个微小的变化重新编译。

标签: c++ json qt qml


【解决方案1】:

您正在清除 mItems,然后在 onReceive 方法中将新项目附加到它。您没有显示模型是否正在侦听 postItemRefresh 信号。不管怎样,你应该打电话给beginResetModelendResetModel

例如将以下插槽添加到您的 TickerModel 并将其连接到 postItemRefresh 信号以使您的模型与新数据同步:

void TickerModel::reset()
{
    beginResetModel();
    endResetModel();
}

编辑:

当事情搞砸时,您应该添加打印或使用调试器使用您的模型 data() 调用的参数。此外,请确保在更新项目时调用 beginResetModel 和 endResetModel。

您的rowCount 是否返回mClient-&gt;items().count()

处理更复杂的json对象,而不是:

QJsonArray currencies = jsonDoc.array();
for(int i = 0; i < currencies.count(); i++) {
    QJsonObject currency = currencies[i].toObject();
    mItems.append(currency.toVariantMap());
}

您不处理 QVariantMap,而是将更复杂的 json 对象解析为您自己的类,该类为数据提供 getter:

QJsonArray currencies = jsonDoc.array();
for(int i = 0; i < currencies.count(); i++) {
    QJsonObject currency = currencies[i].toObject();
    ComplexCurrency c;
    c.read(currency);
    mItems.append(c);
}

你的班级:

class ComplexCurrency
{
public:
    int id() const;
    QString name() const;
    bool enabled() const;
    QList<QVariantMap> items();

    void read(const QJsonObject &json);

private:
    int m_id;
    QString m_name;
    bool m_enabled;
    QList<QVariantMap> m_items;
    ...
};

void ComplexCurrency::read(const QJsonObject &json)
{
    m_id = json["id"].toInt();
    m_name = json["name"].toString();
    m_enabled = json["enabled"].toBool();
    // parse items array here
}

然后在模型data()中:

QVariant TickerModel::data(const QModelIndex &index, int role) const
{
    if ((index.row() < 0 || index.row() >= mClient->items().count()) || !mClient)
        return QVariant();

    const ComplexCurrency &c = mClient->items().at(index.row());
    if (role == NameRole)
        return c.name();
    else if (role == SomethingRole)
        return c.something();

    return QVariant();
}

【讨论】:

  • 您好,感谢您的回答。我一定错过了添加TickerModel::setClient(TickerClient *) 的代码,它将回调连接到preItemRefreshpostItemRefresh 调用beginResetModelendResetModel
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多