#(66):訪問網絡(2)
上一章我們了解了`NetWorker`類的簡單實現。不僅如此,我們還提到了幾個 C++ 開發時常用的設計模式。這些在接下來得代碼中依然會用到。
現在我們先來研究下?[OpenWeatherMap 的相關 API](http://api.openweathermap.org/api)。之所以選擇 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](http://api.openweathermap.org/data/2.5/weather?q=Beijing,cn&mode=json&units=metric&lang=zh_cn)。下面是這個鏈接的參數分析:
| 參數名字 | 傳入值 | 說明 |
| -- || -- |
| q | Beijing,cn | 查詢中國北京的天氣 |
| mode | json | 返回格式為 JSON |
| units | metric | 返回單位為公制 |
| lang | zh_cn | 返回語言為中文 |
點擊鏈接,服務器返回一個 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}
~~~
我們從[這里](http://bugs.openweathermap.org/projects/api/wiki/Weather_Data)找到 JSON 各個字段的含義。現在我們關心的是:時間(dt);氣溫(temp);氣壓(pressure);濕度(humidity)和天氣狀況(weather)。基于此,我們設計了`WeatherInfo`類,用于封裝服務器返回的信息:
~~~
class WeatherDetail
{
public:
WeatherDetail();
~WeatherDetail();
QString desc() const;
void setDesc(const QString &desc);
QString icon() const;
void setIcon(const QString &icon);
private:
class Private;
friend class Private;
Private *d;
};
class WeatherInfo
{
public:
WeatherInfo();
~WeatherInfo();
QString cityName() const;
void setCityName(const QString &cityName);
quint32 id() const;
void setId(quint32 id);
QDateTime dateTime() const;
void setDateTime(const QDateTime &dateTime);
float temperature() const;
void setTemperature(float temperature);
float humidity() const;
void setHumidity(float humidity);
float pressure() const;
void setPressure(float pressure);
QList<WeatherDetail *> details() const;
void setDetails(const QList<WeatherDetail *> details);
private:
class Private;
friend class Private;
Private *d;
};
QDebug operator <<(QDebug dbg, const WeatherDetail &w);
QDebug operator <<(QDebug dbg, const WeatherInfo &w);
~~~
`WeatherInfo`和`WeatherDetail`兩個類相互合作存儲我們所需要的數據。由于是數據類,所以只有單純的 setter 和 getter 函數,這里不再把源代碼寫出來。值得說明的是最后兩個全局函數:
~~~
QDebug operator <<(QDebug dbg, const WeatherDetail &w);
QDebug operator <<(QDebug dbg, const WeatherInfo &w);
~~~
我們重寫了`<<`運算符,以便能夠使用類似`qDebug() << weatherInfo;`這樣的語句進行調試。它的實現是這樣的:
~~~
QDebug operator <<(QDebug dbg, const WeatherDetail &w)
{
dbg.nospace() << "("
<< "Description: " << w.desc() << "; "
<< "Icon: " << w.icon()
<< ")";
return dbg.space();
}
QDebug operator <<(QDebug dbg, const WeatherInfo &w)
{
dbg.nospace() << "("
<< "id: " << w.id() << "; "
<< "City name: " << w.cityName() << "; "
<< "Date time: " << w.dateTime().toString(Qt::DefaultLocaleLongDate) << ": " << endl
<< "Temperature: " << w.temperature() << ", "
<< "Pressure: " << w.pressure() << ", "
<< "Humidity: " << w.humidity() << endl
<< "Details: [";
foreach (WeatherDetail *detail, w.details()) {
dbg.nospace() << "( Description: " << detail->desc() << ", "
<< "Icon: " << detail->icon() << "), ";
}
dbg.nospace() << "] )";
return dbg.space();
}
~~~
這兩個函數雖然比較長,但是很簡單,這里不再贅述。
下面我們來看主窗口:
~~~
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
class Private;
friend class Private;
Private *d;
};
~~~
正如前面所說的,這里依然使用了 d 指針模式。頭文件沒有什么可說的。`MainWindow::Private`的實現依舊簡單:
~~~
class MainWindow::Private
{
public:
Private()
{
netWorker = NetWorker::instance();
}
void fetchWeather(const QString &cityName) const
{
netWorker->get(QString("http://api.openweathermap.org/data/2.5/weather?q=%1&mode=json&units=metric&lang=zh_cn").arg(cityName));
}
NetWorker *netWorker;
};
~~~
我們將`MainWindow`所需要的`NetWorker`作為`MainWindow::Private`的一個成員變量。`MainWindow::Private`提供了一個`fetchWeather()`函數。由于`NetWorker`提供的函數都是相當底層的,為了提供業務級別的處理,我們將這樣的函數封裝在`MainWindow::Private`中。當然,你也可以在`NetWorker`中直接提供類似的函數,這取決于你的系統分層設計。
~~~
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent),
d(new MainWindow::Private)
{
QComboBox *cityList = new QComboBox(this);
cityList->addItem(tr("Beijing"), QLatin1String("Beijing,cn"));
cityList->addItem(tr("Shanghai"), QLatin1String("Shanghai,cn"));
cityList->addItem(tr("Nanjing"), QLatin1String("Nanjing,cn"));
QLabel *cityLabel = new QLabel(tr("City: "), this);
QPushButton *refreshButton = new QPushButton(tr("Refresh"), this);
QHBoxLayout *cityListLayout = new QHBoxLayout;
cityListLayout->setDirection(QBoxLayout::LeftToRight);
cityListLayout->addWidget(cityLabel);
cityListLayout->addWidget(cityList);
cityListLayout->addWidget(refreshButton);
QVBoxLayout *weatherLayout = new QVBoxLayout;
weatherLayout->setDirection(QBoxLayout::TopToBottom);
QLabel *cityNameLabel = new QLabel(this);
weatherLayout->addWidget(cityNameLabel);
QLabel *dateTimeLabel = new QLabel(this);
weatherLayout->addWidget(dateTimeLabel);
QWidget *mainWidget = new QWidget(this);
QVBoxLayout *mainLayout = new QVBoxLayout(mainWidget);
mainLayout->addLayout(cityListLayout);
mainLayout->addLayout(weatherLayout);
setCentralWidget(mainWidget);
resize(320, 120);
setWindowTitle(tr("Weather"));
connect(d->netWorker, &NetWorker::finished, [=] (QNetworkReply *reply) {
qDebug() << reply;
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<WeatherDetail *> 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);
}
weather.setDetails(details);
cityNameLabel->setText(weather.cityName());
dateTimeLabel->setText(weather.dateTime().toString(Qt::DefaultLocaleLongDate));
}
} else {
QMessageBox::critical(this, tr("Error"), error.errorString());
}
reply->deleteLater();
});
connect(refreshButton, &QPushButton::clicked, [=] () {
d->fetchWeather(cityList->itemData(cityList->currentIndex()).toString());
});
}
MainWindow::~MainWindow()
{
delete d;
d = 0;
}
~~~
接下來我們來看`MainWindow`的構造函數和析構函數。構造函數雖然很長但是并不復雜,主要是對界面的構建。我們這里略過這些界面的代碼,直接看兩個信號槽的連接。
~~~
connect(d->netWorker, &NetWorker::finished, [=] (QNetworkReply *reply) {
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<WeatherDetail *> 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);
}
weather.setDetails(details);
cityNameLabel->setText(weather.cityName());
dateTimeLabel->setText(weather.dateTime().toString(Qt::DefaultLocaleLongDate));
}
} else {
QMessageBox::critical(this, tr("Error"), error.errorString());
}
reply->deleteLater();
});
connect(refreshButton, &QPushButton::clicked, [=] () {
d->fetchWeather(cityList->itemData(cityList->currentIndex()).toString());
});
~~~
由于使用了 Qt5,我們選擇新的連接語法。第一個`connect()`函數中,我們按照 API 文檔中描述的那樣對服務器返回的 JSON 字符串進行解析,然后將數據填充到一個`WeatherInfo`的對象。然后操作界面的兩個控件顯示數據。值得注意的是函數的最后一行,`reply->deleteLater();`。當網絡請求結束時,delete 服務器返回的`QNetworkReply`對象是用戶的責任。用戶需要選擇一個恰當的時機進行 delete 操作。但是,我們不能直接在`finiahed()`信號對應的槽函數中調用`delete`運算符。相反,我們需要使用`deleteLater()`函數,正如前面代碼中顯示的那樣。第二個槽函數則相對簡單,僅僅是重新獲取新的數據。
選擇我們可以運行下程序了:
[](http://files.devbean.net/images/2013/10/weather-demo.png)
- (1)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)