现在我们先来研究下 。之所以选择 OpenWeatherMap,主要是因为这个网站提供了简洁的 API 接口,非常适合示例程序,并且其开发也不需要额外申请 App ID。OpenWeatherMap 的 API 可以选择返回 JSON 或者 XML,这里我们选择使用 JSON 格式。在进行查询时,OpenWeatherMap 支持使用城市名、地理经纬度以及城市 ID,为简单起见,我们选择使用城市名。我们先来看一个例子:http://api.openweathermap.org/data/2.5/weather?q=Beijing,cn&mode=json&units=metric&lang=zh_cn。下面是这个链接的参数分析:

    点击链接,服务器返回一个 JSON 字符串(此时你应该能够使用浏览器看到这个字符串):

    我们从找到 JSON 各个字段的含义。现在我们关心的是:时间(dt);气温(temp);气压(pressure);湿度(humidity)和天气状况(weather)。基于此,我们设计了WeatherInfo类,用于封装服务器返回的信息:

    1. {
    2. public:
    3. WeatherDetail();
    4. ~WeatherDetail();
    5.  
    6. QString desc() const;
    7. void setDesc(const QString &desc);
    8.  
    9. QString icon() const;
    10. void setIcon(const QString &icon);
    11.  
    12. private:
    13. class Private;
    14. friend class Private;
    15. Private *d;
    16. };
    17.  
    18. class WeatherInfo
    19. {
    20. public:
    21. WeatherInfo();
    22. ~WeatherInfo();
    23.  
    24. QString cityName() const;
    25. void setCityName(const QString &cityName);
    26.  
    27. quint32 id() const;
    28. void setId(quint32 id);
    29.  
    30. QDateTime dateTime() const;
    31. void setDateTime(const QDateTime &dateTime);
    32.  
    33. float temperature() const;
    34. void setTemperature(float temperature);
    35.  
    36. float humidity() const;
    37. void setHumidity(float humidity);
    38.  
    39. float pressure() const;
    40. void setPressure(float pressure);
    41.  
    42. QList<WeatherDetail *> details() const;
    43.  
    44. private:
    45. class Private;
    46. friend class Private;
    47. Private *d;
    48. };
    49.  
    50. QDebug operator <<(QDebug dbg, const WeatherDetail &w);
    51. QDebug operator <<(QDebug dbg, const WeatherInfo &w);

    我们重写了<<运算符,以便能够使用类似qDebug() << weatherInfo;这样的语句进行调试。它的实现是这样的:

    1. QDebug operator <<(QDebug dbg, const WeatherDetail &w)
    2. {
    3. dbg.nospace() << "("
    4. << "Description: " << w.desc() << "; "
    5. << "Icon: " << w.icon()
    6. << ")";
    7. return dbg.space();
    8. }
    9.  
    10. QDebug operator <<(QDebug dbg, const WeatherInfo &w)
    11. {
    12. dbg.nospace() << "("
    13. << "id: " << w.id() << "; "
    14. << "City name: " << w.cityName() << "; "
    15. << "Date time: " << w.dateTime().toString(Qt::DefaultLocaleLongDate) << ": " << endl
    16. << "Temperature: " << w.temperature() << ", "
    17. << "Pressure: " << w.pressure() << ", "
    18. << "Humidity: " << w.humidity() << endl
    19. << "Details: [";
    20. foreach (WeatherDetail *detail, w.details()) {
    21. dbg.nospace() << "( Description: " << detail->desc() << ", "
    22. << "Icon: " << detail->icon() << "), ";
    23. }
    24. dbg.nospace() << "] )";
    25. return dbg.space();
    26. }

    这两个函数虽然比较长,但是很简单,这里不再赘述。

    下面我们来看主窗口:

    1. class MainWindow::Private
    2. {
    3. public:
    4. Private()
    5. {
    6. netWorker = NetWorker::instance();
    7. }
    8. void fetchWeather(const QString &cityName) const
    9. {
    10. netWorker->get(QString("http://api.openweathermap.org/data/2.5/weather?q=%1&mode=json&units=metric&lang=zh_cn").arg(cityName));
    11. }
    12.  
    13. NetWorker *netWorker;
    14. };

    我们将所需要的NetWorker作为MainWindow::Private的一个成员变量。MainWindow::Private提供了一个fetchWeather()函数。由于NetWorker提供的函数都是相当底层的,为了提供业务级别的处理,我们将这样的函数封装在MainWindow::Private中。当然,你也可以在中直接提供类似的函数,这取决于你的系统分层设计。

    接下来我们来看MainWindow的构造函数和析构函数。构造函数虽然很长但是并不复杂,主要是对界面的构建。我们这里略过这些界面的代码,直接看两个信号槽的连接。

    1. connect(d->netWorker, &NetWorker::finished, [=] (QNetworkReply *reply) {
    2. QJsonParseError error;
    3. QJsonDocument jsonDocument = QJsonDocument::fromJson(reply->readAll(), &error);
    4. if (error.error == QJsonParseError::NoError) {
    5. if (!(jsonDocument.isNull() || jsonDocument.isEmpty()) && jsonDocument.isObject()) {
    6. QVariantMap data = jsonDocument.toVariant().toMap();
    7. WeatherInfo weather;
    8. weather.setCityName(data[QLatin1String("name")].toString());
    9. QDateTime dateTime;
    10. dateTime.setTime_t(data[QLatin1String("dt")].toLongLong());
    11. weather.setDateTime(dateTime);
    12. QVariantMap main = data[QLatin1String("main")].toMap();
    13. weather.setTemperature(main[QLatin1String("temp")].toFloat());
    14. weather.setPressure(main[QLatin1String("pressure")].toFloat());
    15. weather.setHumidity(main[QLatin1String("humidity")].toFloat());
    16. QVariantList detailList = data[QLatin1String("weather")].toList();
    17. QList<WeatherDetail *> details;
    18. foreach (QVariant w, detailList) {
    19. QVariantMap wm = w.toMap();
    20. WeatherDetail *detail = new WeatherDetail;
    21. detail->setDesc(wm[QLatin1String("description")].toString());
    22. detail->setIcon(wm[QLatin1String("icon")].toString());
    23. details.append(detail);
    24. }
    25. weather.setDetails(details);
    26.  
    27. cityNameLabel->setText(weather.cityName());
    28. dateTimeLabel->setText(weather.dateTime().toString(Qt::DefaultLocaleLongDate));
    29. }
    30. } else {
    31. QMessageBox::critical(this, tr("Error"), error.errorString());
    32. }
    33. reply->deleteLater();
    34. });
    35. connect(refreshButton, &QPushButton::clicked, [=] () {
    36. });

    由于使用了 Qt5,我们选择新的连接语法。第一个connect()函数中,我们按照 API 文档中描述的那样对服务器返回的 JSON 字符串进行解析,然后将数据填充到一个WeatherInfo的对象。然后操作界面的两个控件显示数据。值得注意的是函数的最后一行,reply->deleteLater();。当网络请求结束时,delete 服务器返回的QNetworkReply对象是用户的责任。用户需要选择一个恰当的时机进行 delete 操作。但是,我们不能直接在finiahed()信号对应的槽函数中调用运算符。相反,我们需要使用deleteLater()函数,正如前面代码中显示的那样。第二个槽函数则相对简单,仅仅是重新获取新的数据。