Qt 学习之路 2(67):访问网络(3)

上一章我们了解了如何使用我们设计的NetWorker类实现我们所需要的网络操作。本章我们将继续完善前面介绍的天气程序。

注意到我们在WeatherDetail类中有一个icon属性。到现在为止我们还没有用到这个属性。下面我们考虑如何修改我们的程序。

通过查看 OpenWeatherMap 的相关 API 我们可以发现,当我们查询天气时会附带这么一个 icon 属性。这个属性其实是网站上的一个天气的图片。还是以上一章我们见到的 JSON 返回值为例:

 
 
{"coord":{"lon":116.397232,"lat":39.907501},"sys":{"country":"CN","sunrise":1381530122,"sunset":1381570774},"weather":[{"id":800,"main":"Clear","description":"晴","icon":"01d"}],"base":"gdps stations","main":{"temp":20,"pressure":1016,"humidity":34,"temp_min":20,"temp_max":20},"wind":{"speed":2,"deg":50},"clouds":{"all":0},"dt":1381566600,"id":1816670,"name":"Beijing","cod":200}
1
{"coord":{"lon":116.397232,"lat":39.907501},"sys":{"country":"CN","sunrise":1381530122,"sunset":1381570774},"weather":[{"id":800,"main":"Clear","description":"晴","icon":"01d"}],"base":"gdps stations","main":{"temp":20,"pressure":1016,"humidity":34,"temp_min":20,"temp_max":20},"wind":{"speed":2,"deg":50},"clouds":{"all":0},"dt":1381566600,"id":1816670,"name":"Beijing","cod":200}

注意到其中的 icon:01d 这个键值对。通过文档我们知道,01d 实际对应于网站上的一张图片:http://openweathermap.org/img/w/01d.png。这就是我们的思路:当我们获取到实际天气数据时,我们根据这个返回值从网站获取到图片,然后显示到我们的程序中。

回忆下我们的NetWorker类的实现。我们将其finished()信号与我们自己实现的槽函数连接起来,其代码大致相当于:

 
 
connect(d->netWorker, &NetWorker::finished, [=] (QNetworkReply *reply) {
...
});
1
2
3
connect(d->netWorker, &NetWorker::finished, [=] (QNetworkReply *reply) {
    ...
});

我们将finished()信号与一个 Lambda 表达式连接起来,其参数就是服务器的响应值。这样一来就会有一个问题:我们实际是有两次网络请求,第一次是向服务器请求当前的天气情况,第二次是根据第一次响应值去请求一张图片。每次网络请求完成时都会发出finished()信号,这就要求我们在槽函数中区分当前到底是哪一个请求的返回。所以,我们需要修改下有关网络请求的代码:

 
 
class NetWorker : public QObject
{
...
QNetworkReply *get(const QString &url);
...
};

...

QNetworkReply * NetWorker::get(const QString &url)
{
return d->manager->get(QNetworkRequest(QUrl(url)));
}

1
2
3
4
5
6
7
8
9
10
11
12
13
class NetWorker : public QObject
{
    ...
    QNetworkReply *get(const QString &url);
    ...
};
 
...
 
QNetworkReply * NetWorker::get(const QString &url)
{
    return d->manager->get(QNetworkRequest(QUrl(url)));
}

首先要修改的是NetWorker类的get()函数。我们要让这个函数返回一个QNetworkReply *变量。这个对象其实是QNetworkAccessManager::get()函数的返回值,我们简单地将其返回出来。接下来要修改的是MainWindow::Private的代码:

 
 
class MainWindow::Private
{
public:
Private(MainWindow *q) :
mainWindow(q)
{
netWorker = NetWorker::instance();
}

void fetchWeather(const QString &cityName)
{
QNetworkReply *reply = netWorker->get(QString("http://api.openweathermap.org/data/2.5/weather?q=%1&mode=json&units=metric&lang=zh_cn").arg(cityName));
replyMap.insert(reply, FetchWeatherInfo);
}

void fetchIcon(const QString &iconName)
{
QNetworkReply *reply = netWorker->get(QString("http://openweathermap.org/img/w/%1.png").arg(iconName));
replyMap.insert(reply, FetchWeatherIcon);
}

NetWorker *netWorker;
MainWindow *mainWindow;
QMap<QNetworkReply *, RemoteRequest> replyMap;
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class MainWindow::Private
{
public:
    Private(MainWindow *q) :
        mainWindow(q)
    {
        netWorker = NetWorker::instance();
    }
 
    void fetchWeather(const QString &cityName)
    {
        QNetworkReply *reply = netWorker->get(QString("http://api.openweathermap.org/data/2.5/weather?q=%1&mode=json&units=metric&lang=zh_cn").arg(cityName));
        replyMap.insert(reply, FetchWeatherInfo);
    }
 
    void fetchIcon(const QString &iconName)
    {
        QNetworkReply *reply = netWorker->get(QString("http://openweathermap.org/img/w/%1.png").arg(iconName));
        replyMap.insert(reply, FetchWeatherIcon);
    }
 
    NetWorker *netWorker;
    MainWindow *mainWindow;
    QMap<QNetworkReply *, RemoteRequest> replyMap;
};

我们的请求是在MainWindow::Private私有类中完成的,为此添加了一个QMap属性。注意我们在原有的fetchWeather()和新增的fetchIcon()函数中都将NetWorker::get()函数的返回值保存下来。RemoteRequest只是一个枚举,定义如下:

 
 
enum RemoteRequest {
FetchWeatherInfo,
FetchWeatherIcon
};
1
2
3
4
enum RemoteRequest {
    FetchWeatherInfo,
    FetchWeatherIcon
};

显然,我们的代码能够清晰地描述出我们的网络请求的返回结果对应于哪一种操作:fetchWeather()NetWorker::get()函数的返回值对应于FetchWeatherInfo操作,而fetchIcon()NetWorker::get()函数的返回值则对应于FetchWeatherIcon操作。我们不需要区分每种操作的具体 URL 地址,因为我们的响应依照操作的不同而不同,与 URL 无关。

下面我们只看槽函数的改变:

 
 
connect(d->netWorker, &NetWorker::finished, [=] (QNetworkReply *reply) {
RemoteRequest request = d->replyMap.value(reply);
switch (request) {
case FetchWeatherInfo:
{
QJsonParseError error;
QJsonDocument jsonDocument = QJsonDocument::fromJson(reply->readAll(), &error);
if (error.error == QJsonParseError::NoError) {
if (!(jsonDocument.isNull() || jsonDocument.isEmpty()) && jsonDocument.isObject()) {
QVariantMap data = jsonDocument.toVariant().toMap();
WeatherInfo weather;
weather.setCityName(data[QLatin1String("name")].toString());
QDateTime dateTime;
dateTime.setTime_t(data[QLatin1String("dt")].toLongLong());
weather.setDateTime(dateTime);
QVariantMap main = data[QLatin1String("main")].toMap();
weather.setTemperature(main[QLatin1String("temp")].toFloat());
weather.setPressure(main[QLatin1String("pressure")].toFloat());
weather.setHumidity(main[QLatin1String("humidity")].toFloat());
QVariantList detailList = data[QLatin1String("weather")].toList();
QList details;
foreach (QVariant w, detailList) {
QVariantMap wm = w.toMap();
WeatherDetail *detail = new WeatherDetail;
detail->setDesc(wm[QLatin1String("description")].toString());
detail->setIcon(wm[QLatin1String("icon")].toString());
details.append(detail);

QHBoxLayout *weatherDetailLayout = new QHBoxLayout;
weatherDetailLayout->setDirection(QBoxLayout::LeftToRight);
weatherDetailLayout->addWidget(new QLabel(detail->desc(), this));
weatherDetailLayout->addWidget(new QLabel(this));
weatherLayout->addLayout(weatherDetailLayout);

d->fetchIcon(detail->icon());
}
weather.setDetails(details);

cityNameLabel->setText(weather.cityName());
dateTimeLabel->setText(weather.dateTime().toString(Qt::DefaultLocaleLongDate));
}
} else {
QMessageBox::critical(this, tr("Error"), error.errorString());
}
break;
}
case FetchWeatherIcon:
{
QHBoxLayout *weatherDetailLayout = (QHBoxLayout *)weatherLayout->itemAt(2)->layout();
QLabel *iconLabel = (QLabel *)weatherDetailLayout->itemAt(1)->widget();
QPixmap pixmap;
pixmap.loadFromData(reply->readAll());
iconLabel->setPixmap(pixmap);
break;
}
}

reply->deleteLater();
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
connect(d->netWorker, &NetWorker::finished, [=] (QNetworkReply *reply) {
    RemoteRequest request = d->replyMap.value(reply);
    switch (request) {
    case FetchWeatherInfo:
    {
        QJsonParseError error;
        QJsonDocument jsonDocument = QJsonDocument::fromJson(reply->readAll(), &error);
        if (error.error == QJsonParseError::NoError) {
            if (!(jsonDocument.isNull() || jsonDocument.isEmpty()) && jsonDocument.isObject()) {
                QVariantMap data = jsonDocument.toVariant().toMap();
                WeatherInfo weather;
                weather.setCityName(data[QLatin1String("name")].toString());
                QDateTime dateTime;
                dateTime.setTime_t(data[QLatin1String("dt")].toLongLong());
                weather.setDateTime(dateTime);
                QVariantMap main = data[QLatin1String("main")].toMap();
                weather.setTemperature(main[QLatin1String("temp")].toFloat());
                weather.setPressure(main[QLatin1String("pressure")].toFloat());
                weather.setHumidity(main[QLatin1String("humidity")].toFloat());
                QVariantList detailList = data[QLatin1String("weather")].toList();
                QList details;
                foreach (QVariant w, detailList) {
                    QVariantMap wm = w.toMap();
                    WeatherDetail *detail = new WeatherDetail;
                    detail->setDesc(wm[QLatin1String("description")].toString());
                    detail->setIcon(wm[QLatin1String("icon")].toString());
                    details.append(detail);
 
                    QHBoxLayout *weatherDetailLayout = new QHBoxLayout;
                    weatherDetailLayout->setDirection(QBoxLayout::LeftToRight);
                    weatherDetailLayout->addWidget(new QLabel(detail->desc(), this));
                    weatherDetailLayout->addWidget(new QLabel(this));
                    weatherLayout->addLayout(weatherDetailLayout);
 
                    d->fetchIcon(detail->icon());
                }
                weather.setDetails(details);
 
                cityNameLabel->setText(weather.cityName());
                dateTimeLabel->setText(weather.dateTime().toString(Qt::DefaultLocaleLongDate));
            }
        } else {
            QMessageBox::critical(this, tr("Error"), error.errorString());
        }
        break;
    }
    case FetchWeatherIcon:
    {
        QHBoxLayout *weatherDetailLayout = (QHBoxLayout *)weatherLayout->itemAt(2)->layout();
        QLabel *iconLabel = (QLabel *)weatherDetailLayout->itemAt(1)->widget();
        QPixmap pixmap;
        pixmap.loadFromData(reply->readAll());
        iconLabel->setPixmap(pixmap);
        break;
    }
    }
 
    reply->deleteLater();
});

槽函数最大的变化是,我们依照MainWindow::Private中保存的对应值,找到这个reply对应的操作类型,然后使用一个switch语句进行区分。注意我们在FetchWeatherInfo操作的foreach循环中增加了对WeatherDetail数据的显示。在末尾使用一个d->fetchIcon(detail->icon())语句从网络获取对应的图片。在FetchWeatherIcon操作中,我们根据QHBoxLayoutitemAt()函数找到先前添加的用于显示图片的 label,然后读取 reply 的数据值,以二进制的形式加载图片。虽然代码很长,有些函数我们也是第一次见到,但是整体思路很简单。下面来看最终的运行结果:

我们今天介绍了这种技术,用于区分一个程序中的多次网络请求(这在一个应用中是经常遇到的)。当然这只是其中一种解决方案,如果你有更好的解决方案,也请留言告诉豆子~

Qt 学习之路 2(67):访问网络(3)的更多相关文章

  1. .Net程序员安卓学习之路2:访问网络API

    做应用型的APP肯定是要和网络交互的,那么本节就来实战一把Android访问网络API,还是使用上节的DEMO: 一.准备API: 一般都采用Json作为数据交换格式,目前各种语言均能输出Json串. ...

  2. Qt 学习之路 2(66):访问网络(2)

    Home / Qt 学习之路 2 / Qt 学习之路 2(66):访问网络(2) Qt 学习之路 2(66):访问网络(2)  豆子  2013年10月31日  Qt 学习之路 2  27条评论 上一 ...

  3. Qt 学习之路 2(68):访问网络(4)

    Home / Qt 学习之路 2 / Qt 学习之路 2(68):访问网络(4) Qt 学习之路 2(68):访问网络(4) 豆子 2013年11月7日 Qt 学习之路 2 19条评论 前面几章我们了 ...

  4. Qt 学习之路 2(65):访问网络(1)

    Home / Qt 学习之路 2 / Qt 学习之路 2(65):访问网络(1) Qt 学习之路 2(65):访问网络(1)  豆子  2013年10月11日  Qt 学习之路 2  18条评论 现在 ...

  5. Qt 学习之路 2(72):线程和事件循环

    Qt 学习之路 2(72):线程和事件循环 <理解不清晰,不透彻>  --  有需求的话还需要进行专题学习  豆子  2013年11月24日  Qt 学习之路 2  34条评论 前面一章我 ...

  6. Qt 学习之路 2(71):线程简介

    Qt 学习之路 2(71):线程简介 豆子 2013年11月18日 Qt 学习之路 2 30条评论 前面我们讨论了有关进程以及进程间通讯的相关问题,现在我们开始讨论线程.事实上,现代的程序中,使用线程 ...

  7. Qt 学习之路 2(70):进程间通信

    Qt 学习之路 2(70):进程间通信 豆子 2013年11月12日 Qt 学习之路 2 16条评论 上一章我们了解了有关进程的基本知识.我们将进程理解为相互独立的正在运行的程序.由于二者是相互独立的 ...

  8. Qt 学习之路 2(69):进程

    Qt 学习之路 2(69):进程 豆子 2013年11月9日 Qt 学习之路 2 15条评论 进程是操作系统的基础之一.一个进程可以认为是一个正在执行的程序.我们可以把进程当做计算机运行时的一个基础单 ...

  9. Qt 学习之路 2(35):文件

    Qt 学习之路 2(35):文件 豆子 2013年1月5日 Qt 学习之路 2 12条评论 文件操作是应用程序必不可少的部分.Qt 作为一个通用开发库,提供了跨平台的文件操作能力.从本章开始,我们来了 ...

随机推荐

  1. 八数码问题——A*大法好

    [描述] 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示. 空格周围的棋子可以移到空格中. 要求解的问题是:给出一种初始布局(初始状态)和目标布局( ...

  2. 【总结整理】pv、uv

    1.pv的全称是page view,译为页面浏览量或点击量,通常是衡量一个网站甚至一条网络新闻的指标.用户每次对网站中的一个页面的请求或访问均被记录1个PV,用户对同一页面的多次访问,pv累计.例如, ...

  3. 【总结整理】原创概念原创idea---摘自《结网》

    假如你有一个原创想法,搜索引擎是否已有现成产品与自己的想法一致,如果有,研究他可以节省很多摸索的时间:若没有,那就是一个货真价实的原创idea: 第一类:受到现有产品的启发,将既有概念进行了转换. 第 ...

  4. linux上mysql安装

    所有平台的MySQL下载地址为: MySQL 下载. 挑选你需要的 MySQL Community Server 版本及对应的平台. MySQL - MySQL服务器.你需要该选项,除非你只想连接运行 ...

  5. c++ 指向类成员函数的函数指针

    // ConsoleApplication34.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream& ...

  6. Tomcat 与 数据库连接池 的小坑

    连接池的优点众所周知. 我们可以自己实现数据库连接池,也可引入实现数据库连接池的jar包,按要求进行配置后直接使用. 关于这方面的资料,好多dalao博客上记录的都是旧版本Tomcat的配置方式,很可 ...

  7. h5存储的优点

    1.解决4k大小问题2.解决请求头常带存储信息的问题3.解决关系型存储问题4.可以跨浏览器

  8. 01 git 概念

    本文转自“廖雪峰的git教程” 集中式版本控制系统:版本库是集中存放在中央服务器的,中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆. 分布式版 ...

  9. CentOS7下源码包方式安装rabbitmq

    1.先安装erlang http://www.cnblogs.com/justphp/p/6093880.html 2.下载rabbitmq rpm包: wget http://www.rabbitm ...

  10. jstl c

    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> 例子:list中有两 ...