#(67):訪問網絡(3)
[上一章](http://www.devbean.net/2013/10/qt-study-road-2-access-network-2)我們了解了如何使用我們設計的`NetWorker`類實現我們所需要的網絡操作。本章我們將繼續完善前面介紹的天氣程序。
注意到我們在`WeatherDetail`類中有一個`icon`屬性。到現在為止我們還沒有用到這個屬性。下面我們考慮如何修改我們的程序。
通過查看?[OpenWeatherMap 的相關 API](http://api.openweathermap.org/api)?我們可以發現,當我們查詢天氣時會附帶這么一個 icon 屬性。這個屬性其實是網站上的一個天氣的圖片。還是以上一章我們見到的 JSON 返回值為例:
~~~
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](http://openweathermap.org/img/w/10d.png)。這就是我們的思路:當我們獲取到實際天氣數據時,我們根據這個返回值從網站獲取到圖片,然后顯示到我們的程序中。
回憶下我們的`NetWorker`類的實現。我們將其`finished()`信號與我們自己實現的槽函數連接起來,其代碼大致相當于:
~~~
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)));
}
~~~
首先要修改的是`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;
};
~~~
我們的請求是在`MainWindow::Private`私有類中完成的,為此添加了一個`QMap`屬性。注意我們在原有的`fetchWeather()`和新增的`fetchIcon()`函數中都將`NetWorker::get()`函數的返回值保存下來。`RemoteRequest`只是一個枚舉,定義如下:
~~~
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();
});
~~~
槽函數最大的變化是,我們依照`MainWindow::Private`中保存的對應值,找到這個`reply`對應的操作類型,然后使用一個`switch`語句進行區分。注意我們在`FetchWeatherInfo`操作的`foreach`循環中增加了對`WeatherDetail`數據的顯示。在末尾使用一個`d->fetchIcon(detail->icon())`語句從網絡獲取對應的圖片。在`FetchWeatherIcon`操作中,我們根據`QHBoxLayout`的`itemAt()`函數找到先前添加的用于顯示圖片的 label,然后讀取 reply 的數據值,以二進制的形式加載圖片。雖然代碼很長,有些函數我們也是第一次見到,但是整體思路很簡單。下面來看最終的運行結果:
[](http://files.devbean.net/images/2013/11/weather-icon-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(續)