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

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

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

    1. ...
    2. });

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

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

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

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

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

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

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