一 Qt版本
Qt Creator 4.4.1 Based on Qt 5.9.3 (MSVC 2015, 32 bit)
二 项目效果图
三 代码部分
pro文件添加 QT += network
3.1 头文件 weather.h
#ifndef WEATHER_H #define WEATHER_H #include <QWidget> #include <QList> #include <QLabel> #include <QPoint> #include <QRect> #include <QTimer> #include <QMouseEvent> #include <QMenu> #include <QAction> #include <QNetworkAccessManager> #include <QNetworkReply> #include <QNetworkRequest> #include <QByteArray> namespace Ui { class Weather; } struct Today { QString date; QString wendu; QString city; QString shidu; QString pm25; QString quality; QString ganmao; QString fx; QString fl; QString type; QString sunrise; QString sunset; QString notice; }; struct Forecast { QString date; QString high; QString low; QString aqi; QString type; }; class Weather : public QWidget { Q_OBJECT public: explicit Weather(QWidget *parent = 0); ~Weather(); protected: bool eventFilter(QObject *watched, QEvent *event); void mouseMoveEvent(QMouseEvent *event); void mousePressEvent(QMouseEvent *event); void contextMenuEvent(QContextMenuEvent *menuEvent); private slots: void replayFinished(QNetworkReply *reply); void on_searchBt_clicked(); void on_refreshBt_clicked(); void slot_exitApp(); private: Ui::Weather *ui; QPoint mPos; // 窗口移动 QList<QLabel *> forecast_week_list; QList<QLabel *> forecast_date_list; QList<QLabel *> forecast_aqi_list; QList<QLabel *> forecast_type_list; QList<QLabel *> forecast_typeIco_list; QList<QLabel *> forecast_high_list; QList<QLabel *> forecast_low_list; Today today; Forecast forecast[6]; static const QPoint sun[2]; static const QRect sunRizeSet[2]; static const QRect rect[2]; QTimer *sunTimer; QNetworkAccessManager *manager; QString url; QString city; QString cityTmp; /* 右键菜单退出程序 */ QMenu *m_pMenu; QAction *m_pExitAct; void getWeatherInfo(QNetworkAccessManager *manager); void parseJson(QByteArray bytes); void setLabelContent(); void paintSunRiseSet(); void paintCurve(); void callKeyBoard(); }; #endif // WEATHER_H
3.2 核心代码 weather.cpp
#include "weather.h" #include "ui_weather.h" //#include "keyboard.h" #include <QPainter> #include <QDateTime> #include <QMessageBox> #include <QJsonDocument> #include <QJsonParseError> #include <QJsonObject> #include <QJsonValue> #include <QJsonArray> #define SPAN_INDEX 3 // 温度曲线间隔指数 #define ORIGIN_SIZE 3 // 温度曲线原点大小 #define TEMPERATURE_STARTING_COORDINATE 45 // 高温平均值起始坐标 // 日出日落底线 const QPoint Weather::sun[2] = { QPoint(20, 75), QPoint(130, 75) }; // 日出日落时间 const QRect Weather::sunRizeSet[2] = { QRect(0, 80, 50, 20), QRect(100, 80, 50, 20) }; // 日出日落圆弧 const QRect Weather::rect[2] = { QRect(25, 25, 100, 100), // 虚线圆弧 QRect(50, 80, 50, 20) // “日出日落”文本 }; Weather::Weather(QWidget *parent) : QWidget(parent), ui(new Ui::Weather) { ui->setupUi(this); this->setWindowFlag(Qt::FramelessWindowHint); ui->cityLineEdit->setStyleSheet("QLineEdit{border: 1px solid gray; border-radius: 4px; background:argb(47, 47, 47, 130); color:rgb(255, 255, 255);} QLineEdit:hover{border-color:rgb(101, 255, 106); }"); forecast_week_list << ui->week0Lb << ui->week1Lb << ui->week2Lb << ui->week3Lb << ui->week4Lb << ui->week5Lb; forecast_date_list << ui->date0Lb << ui->date1Lb << ui->date2Lb << ui->date3Lb << ui->date4Lb << ui->date5Lb; forecast_aqi_list << ui->quality0Lb << ui->quality1Lb << ui->quality2Lb << ui->quality3Lb << ui->quality4Lb << ui->quality5Lb; forecast_type_list << ui->type0Lb << ui->type1Lb << ui->type2Lb << ui->type3Lb << ui->type4Lb << ui->type5Lb; forecast_typeIco_list << ui->typeIco0Lb << ui->typeIco1Lb << ui->typeIco2Lb << ui->typeIco3Lb << ui->typeIco4Lb << ui->typeIco5Lb; forecast_high_list << ui->high0Lb << ui->high1Lb << ui->high2Lb << ui->high3Lb << ui->high4Lb << ui->high5Lb; forecast_low_list << ui->low0Lb << ui->low1Lb << ui->low2Lb << ui->low3Lb << ui->low4Lb << ui->low5Lb; // 结构体初始化 today.date = "0000-00-00"; today.city = "null"; today.fl = "无数据"; today.fx = "无数据"; today.ganmao = "无数据"; today.notice = "无数据"; today.pm25 = "无数据"; today.quality = "无数据"; today.shidu = "无数据"; today.sunrise = "00:00"; today.sunset = "00:00"; today.wendu = "null"; today.type = "无数据"; for (int i = 0; i < 6; i++) { forecast[i].aqi = "0"; forecast[i].date = "00日星期0"; forecast[i].high = "高温 0.0℃"; forecast[i].low = "低温 0.0℃"; forecast[i].type = "undefined"; } // 右键菜单 m_pMenu = new QMenu(this); m_pExitAct = new QAction; m_pExitAct->setText( tr("退出") ); m_pExitAct->setIcon(QIcon(":/weatherIco/close.ico")); m_pMenu->addAction(m_pExitAct); connect( m_pExitAct, SIGNAL(triggered(bool)), this, SLOT(slot_exitApp()) ); // dateLb和WeekLb样式表设置 for (int i = 0; i < 6; i++) { forecast_date_list[i]->setStyleSheet("background-color: rgba(0, 255, 255, 100);"); forecast_week_list[i]->setStyleSheet("background-color: rgba(0, 255, 255, 100);"); } // 请求天气API信息 url = "http://www.sojson.com/open/api/weather/json.shtml?city="; city = "南岸"; cityTmp = city; manager = new QNetworkAccessManager(this); connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replayFinished(QNetworkReply*))); getWeatherInfo(manager); /* 事件过滤 */ ui->sunRiseSetLb->installEventFilter(this); // 启用事件过滤器 ui->curveLb->installEventFilter(this); ui->cityLineEdit->installEventFilter(this); sunTimer = new QTimer(ui->sunRiseSetLb); connect(sunTimer, SIGNAL(timeout()), ui->sunRiseSetLb, SLOT(update())); sunTimer->start(1000); // setLabelContent(); } Weather::~Weather() { delete ui; } /* 请求数据 */ void Weather::getWeatherInfo(QNetworkAccessManager *manager) { QUrl jsonUrl(url + city); manager->get( QNetworkRequest(jsonUrl) ); } void Weather::replayFinished(QNetworkReply *reply) { /* 获取响应的信息,状态码为200表示正常 --comment by wsg 2017/12/11 */ QVariant status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); if(reply->error() != QNetworkReply::NoError || status_code != 200) { QMessageBox::warning(this, "错误", "天气:请求数据错误,检查网络连接!", QMessageBox::Ok); return; } QByteArray bytes = reply->readAll(); // QString result = QString::fromLocal8Bit(bytes); parseJson(bytes); } /* 解析Json数据 */ void Weather::parseJson(QByteArray bytes) { QJsonParseError err; QJsonDocument jsonDoc = QJsonDocument::fromJson(bytes, &err); // 检测json格式 if (err.error != QJsonParseError::NoError) // Json格式错误 { return; } QJsonObject jsObj = jsonDoc.object(); QString message = jsObj.value("message").toString(); if (message != "Success !") { QMessageBox::information(this, tr("The information of Json_desc"), tr("天气:城市错误!"), QMessageBox::Ok ); city = cityTmp; return; } QString dateStr = jsObj.value("date").toString(); today.date = QDate::fromString(dateStr, "yyyyMMdd").toString("yyyy-MM-dd"); today.city = jsObj.value("city").toString(); // 解析data QJsonObject dataObj = jsObj.value("data").toObject(); today.shidu = dataObj.value("shidu").toString(); today.pm25 = QString::number( dataObj.value("pm25").toDouble() ); today.quality = dataObj.value("quality").toString(); today.wendu = dataObj.value("wendu").toString() + "°"; today.ganmao = dataObj.value("ganmao").toString(); // 解析data中的yesterday QJsonObject yestObj = dataObj.value("yesterday").toObject(); forecast[0].date = yestObj.value("date").toString(); forecast[0].high = yestObj.value("high").toString(); forecast[0].low = yestObj.value("low").toString(); forecast[0].aqi = QString::number( yestObj.value("aqi").toDouble() ); forecast[0].type = yestObj.value("type").toString(); // 解析data中的forecast QJsonArray forecastArr = dataObj.value("forecast").toArray(); int j = 0; for (int i = 1; i < 6; i++) { QJsonObject dateObj = forecastArr.at(j).toObject(); forecast[i].date = dateObj.value("date").toString(); forecast[i].aqi = QString::number( dateObj.value("aqi").toDouble() ); forecast[i].high = dateObj.value("high").toString(); forecast[i].low = dateObj.value("low").toString(); forecast[i].type = dateObj.value("type").toString(); j++; } // 取得今日数据 QJsonObject todayObj = forecastArr.at(0).toObject(); today.fx = todayObj.value("fx").toString(); today.fl = todayObj.value("fl").toString(); today.type = todayObj.value("type").toString(); today.sunrise = todayObj.value("sunrise").toString(); today.sunset = todayObj.value("sunset").toString(); today.notice = todayObj.value("notice").toString(); setLabelContent(); } /* 设置控件文本 */ void Weather::setLabelContent() { // 今日数据 ui->dateLb->setText(today.date); ui->temLb->setText(today.wendu); ui->cityLb->setText(today.city); ui->typeLb->setText(today.type); ui->noticeLb->setText(today.notice); ui->shiduLb->setText(today.shidu); ui->pm25Lb->setText(today.pm25); ui->fxLb->setText(today.fx); ui->flLb->setText(today.fl); ui->ganmaoBrowser->setText(today.ganmao); // 判断白天还是夜晚图标 QString sunsetTime = today.date + " " + today.sunset; if (QDateTime::currentDateTime() <= QDateTime::fromString(sunsetTime, "yyyy-MM-dd hh:mm")) { ui->typeIcoLb->setStyleSheet( tr("border-image: url(:/day/day/%1.png); background-color: argb(60, 60, 60, 0);").arg(today.type) ); } else { ui->typeIcoLb->setStyleSheet( tr("border-image: url(:/night/night/%1.png); background-color: argb(60, 60, 60, 0);").arg(today.type) ); } ui->qualityLb->setText(today.quality == "" ? "无" : today.quality); if (today.quality == "优") { ui->qualityLb->setStyleSheet("color: rgb(0, 255, 0); background-color: argb(255, 255, 255, 0);"); } else if (today.quality == "良") { ui->qualityLb->setStyleSheet("color: rgb(255, 255, 0); background-color: argb(255, 255, 255, 0);"); } else if (today.quality == "轻度污染") { ui->qualityLb->setStyleSheet("color: rgb(255, 170, 0); background-color: argb(255, 255, 255, 0);"); } else if (today.quality == "重度污染") { ui->qualityLb->setStyleSheet("color: rgb(255, 0, 0); background-color: argb(255, 255, 255, 0);"); } else if (today.quality == "严重污染") { ui->qualityLb->setStyleSheet("color: rgb(170, 0, 0); background-color: argb(255, 255, 255, 0);"); } else { ui->qualityLb->setStyleSheet("color: rgb(255, 255, 255); background-color: argb(255, 255, 255, 0);"); } // 六天数据 for (int i = 0; i < 6; i++) { forecast_week_list[i]->setText(forecast[i].date.right(3)); forecast_date_list[i]->setText(forecast[i].date.left(3)); forecast_type_list[i]->setText(forecast[i].type); forecast_high_list[i]->setText(forecast[i].high.split(" ").at(1)); forecast_low_list[i]->setText(forecast[i].low.split(" ").at(1)); forecast_typeIco_list[i]->setStyleSheet( tr("image: url(:/day/day/%1.png);").arg(forecast[i].type) ); if (forecast[i].aqi.toInt() >= 0 && forecast[i].aqi.toInt() <= 50) { forecast_aqi_list[i]->setText(tr("优质")); forecast_aqi_list[i]->setStyleSheet("color: rgb(0, 255, 0);"); } else if (forecast[i].aqi.toInt() > 50 && forecast[i].aqi.toInt() <= 100) { forecast_aqi_list[i]->setText(tr("良好")); forecast_aqi_list[i]->setStyleSheet("color: rgb(255, 255, 0);"); } else if (forecast[i].aqi.toInt() > 100 && forecast[i].aqi.toInt() <= 150) { forecast_aqi_list[i]->setText(tr("轻度污染")); forecast_aqi_list[i]->setStyleSheet("color: rgb(255, 170, 0);"); } else if (forecast[i].aqi.toInt() > 150 && forecast[i].aqi.toInt() <= 200) { forecast_aqi_list[i]->setText(tr("重度污染")); forecast_aqi_list[i]->setStyleSheet("color: rgb(255, 0, 0);"); } else { forecast_aqi_list[i]->setText(tr("严重污染")); forecast_aqi_list[i]->setStyleSheet("color: rgb(170, 0, 0);"); } }//for ui->week0Lb->setText( tr("昨天") ); ui->week1Lb->setText( tr("今天") ); ui->curveLb->update(); } /* 搜索按钮 */ void Weather::on_searchBt_clicked() { cityTmp = city; city = ui->cityLineEdit->text(); getWeatherInfo(manager); } /* 刷新按钮 */ void Weather::on_refreshBt_clicked() { getWeatherInfo(manager); ui->curveLb->update(); } /* 事件过滤 */ bool Weather::eventFilter(QObject *watched, QEvent *event) { if (watched == ui->sunRiseSetLb && event->type() == QEvent::Paint) { paintSunRiseSet(); } else if (watched == ui->curveLb && event->type() == QEvent::Paint) { paintCurve(); } else if (watched == ui->cityLineEdit && event->type() == QEvent::MouseButtonPress) { callKeyBoard(); //调用软键盘 } return QWidget::eventFilter(watched,event); } /* 日出日落图形绘制 */ void Weather::paintSunRiseSet() { QPainter painter(ui->sunRiseSetLb); painter.setRenderHint(QPainter::Antialiasing, true); // 反锯齿 // 绘制日出日落线和文本 painter.save(); QPen pen = painter.pen(); pen.setWidthF(0.5); pen.setColor(Qt::yellow); painter.setPen(pen); painter.drawLine(sun[0], sun[1]); painter.restore(); painter.save(); painter.setFont( QFont("Microsoft Yahei", 8, QFont::Normal) ); // 字体、大小、正常粗细 painter.setPen(Qt::white); if (today.sunrise != "" && today.sunset != "") { painter.drawText(sunRizeSet[0], Qt::AlignHCenter, today.sunrise); painter.drawText(sunRizeSet[1], Qt::AlignHCenter, today.sunset); } painter.drawText(rect[1], Qt::AlignHCenter, tr("日出日落")); painter.restore(); // 绘制圆弧 painter.save(); // pen.setWidth(1); pen.setWidthF(0.5); pen.setStyle(Qt::DotLine); // 虚线 pen.setColor(Qt::green); painter.setPen(pen); painter.drawArc(rect[0], 0 * 16, 180 * 16); painter.restore(); // 绘制日出日落占比 if (today.sunrise != "" && today.sunset != "") { painter.setPen(Qt::NoPen); painter.setBrush(QColor(255, 85, 0, 100)); int startAngle, spanAngle; QString sunsetTime = today.date + " " + today.sunset; if (QDateTime::currentDateTime() > QDateTime::fromString(sunsetTime, "yyyy-MM-dd hh:mm")) { startAngle = 0 * 16; spanAngle = 180 * 16; } else { // 计算起始角度和跨越角度 static QStringList sunSetTime = today.sunset.split(":"); static QStringList sunRiseTime = today.sunrise.split(":"); static QString sunsetHour = sunSetTime.at(0); static QString sunsetMint = sunSetTime.at(1); static QString sunriseHour = sunRiseTime.at(0); static QString sunriseMint = sunRiseTime.at(1); static int sunrise = sunriseHour.toInt() * 60 + sunriseMint.toInt(); static int sunset = sunsetHour.toInt() * 60 + sunsetMint.toInt(); int now = QTime::currentTime().hour() * 60 + QTime::currentTime().minute(); startAngle = ( (double)(sunset - now) / (sunset - sunrise) ) * 180 * 16; spanAngle = ( (double)(now - sunrise) / (sunset - sunrise) ) * 180 * 16; } if (startAngle >= 0 && spanAngle >= 0) { painter.drawPie(rect[0], startAngle, spanAngle); // 扇形绘制 } } } /* 温度曲线绘制 */ void Weather::paintCurve() { QPainter painter(ui->curveLb); painter.setRenderHint(QPainter::Antialiasing, true); // 反锯齿 // 将温度转换为int类型,并计算平均值,平均值作为curveLb曲线的参考值,参考Y坐标为45 int tempTotal = 0; int high[6] = {}; int low[6] = {}; QString h, l; for (int i = 0; i < 6; i++) { h = forecast[i].high.split(" ").at(1); h = h.left(h.length() - 1); high[i] = (int)(h.toDouble()); tempTotal += high[i]; l = forecast[i].low.split(" ").at(1); l = l.left(h.length() - 1); low[i] = (int)(l.toDouble()); } int tempAverage = (int)(tempTotal / 6); // 最高温平均值 // 算出温度对应坐标 int pointX[6] = {35, 103, 172, 241, 310, 379}; // 点的X坐标 int pointHY[6] = {0}; int pointLY[6] = {0}; for (int i = 0; i < 6; i++) { pointHY[i] = TEMPERATURE_STARTING_COORDINATE - ((high[i] - tempAverage) * SPAN_INDEX); pointLY[i] = TEMPERATURE_STARTING_COORDINATE + ((tempAverage - low[i]) * SPAN_INDEX); } QPen pen = painter.pen(); pen.setWidth(1); // 高温曲线绘制 painter.save(); pen.setColor(QColor(255, 170, 0)); pen.setStyle(Qt::DotLine); painter.setPen(pen); painter.setBrush(QColor(255, 170, 0)); painter.drawEllipse(QPoint(pointX[0], pointHY[0]), ORIGIN_SIZE, ORIGIN_SIZE); painter.drawEllipse(QPoint(pointX[1], pointHY[1]), ORIGIN_SIZE, ORIGIN_SIZE); painter.drawLine(pointX[0], pointHY[0], pointX[1], pointHY[1]); pen.setStyle(Qt::SolidLine); pen.setWidth(1); painter.setPen(pen); for (int i = 1; i < 5; i++) { painter.drawEllipse(QPoint(pointX[i+1], pointHY[i+1]), ORIGIN_SIZE, ORIGIN_SIZE); painter.drawLine(pointX[i], pointHY[i], pointX[i+1], pointHY[i+1]); } painter.restore(); // 低温曲线绘制 pen.setColor(QColor(0, 255, 255)); pen.setStyle(Qt::DotLine); painter.setPen(pen); painter.setBrush(QColor(0, 255, 255)); painter.drawEllipse(QPoint(pointX[0], pointLY[0]), ORIGIN_SIZE, ORIGIN_SIZE); painter.drawEllipse(QPoint(pointX[1], pointLY[1]), ORIGIN_SIZE, ORIGIN_SIZE); painter.drawLine(pointX[0], pointLY[0], pointX[1], pointLY[1]); pen.setColor(QColor(0, 255, 255)); pen.setStyle(Qt::SolidLine); painter.setPen(pen); for (int i = 1; i < 5; i++) { painter.drawEllipse(QPoint(pointX[i+1], pointLY[i+1]), ORIGIN_SIZE, ORIGIN_SIZE); painter.drawLine(pointX[i], pointLY[i], pointX[i+1], pointLY[i+1]); } } /* 调用软键盘 */ void Weather::callKeyBoard() { // KeyBoard *keyBoard = new KeyBoard(0, ui->cityLineEdit); // keyBoard->show(); } /* 窗口移动 */ void Weather::mousePressEvent(QMouseEvent *event) { mPos = event->globalPos() - this->pos(); } void Weather::mouseMoveEvent(QMouseEvent *event) { this->move(event->globalPos() - mPos); } /* 右键菜单 */ void Weather::contextMenuEvent(QContextMenuEvent *menuEvent) { m_pMenu->exec(QCursor::pos()); menuEvent->accept(); } void Weather::slot_exitApp() { qApp->exit(0); }
四 部分说明
4.1 界面设计
如图4.1所示,天气窗口界面由图中①、②、③、④四部分。其中:
(1)第①部分包括日期框(QLabel),城市输入框(QLineEdit),搜索按钮和刷新按钮(QToolButton)。
(2)第②部分包括空气质量图标和空气质量框,实时温度显示框(字体大小设置50pt,白色,字体为"Arial"),城市图标和城市显示框(栅格布局),天气类型显示框,天气图标框,天气提醒框。温度显示框样式表设置为边框角度4px,背景色RGB为(60,60,60),透明度为130;其它控件皆为QLabel,透明度为0。
(3)第③部分采用栅格布局,圆角设置为4px,背景RGB为(60,60,60),透明度为130。感冒指数框(QTextBrowser)设置背景透明度为0。右下角为QLabel,用于绘制日出日落曲线。
图4.1 天气窗口UI设计
(4)第④部分整体采用栅格布局,由43个QLabel控件组成。其中:
①第一行用于显示星期,从左至右依次命令为:week0Lb - week5Lb;
②第二行用于显示日期,从左至右依次命名为:date0Lb - date5Lb;
③第三行用于显示空气质量,从左至右依次命名为:quality0Lb - quality5Lb;
④第四行用于显示天气类型图标,从左至右依次命名为:type0Lb - type5Lb;
⑤第五行用于显示天气类型图标,从左至右依次命名为:typeIco0Lb - typeIco5Lb;
⑥第五行用于显示日最高温度,从左至右依次命名为:high0Lb - high5Lb;
⑦第六行为一整个QLabel,用于绘制日最高、最低温度曲线,命名为:curveLb;
⑧第七行用于显示日最低温度,从左至右依次命名为:low0Lb - low5Lb。
4.2 由API获取天气信息
网络上由很多免费的天气信息API,大致分为两种,一种是获取xml编码格式的天气信息,一种是获取Json编码格式的天气信息。Qt对于Json和xml格式文件都有自己的解析方式,而解析Json更为简单、方便,所以选择解析Json格式的天气信息API。
浏览器输入API,获得的信息如下图(图4.2)所示(以北京为例):
图4.2 天气APi获取天气信息示例
注:API来源于http://www.sojson.com/
Qt5提供了QNetworkAccessManager类来获取网络信息,并且有“finished()”信号来响应槽函数,槽函数获取信息并解析。具体步骤如下:
(1)新建QNetworkAccessManager类对象manager,并连接信号与槽函数:
1 manager = new QNetworkAccessManager(this);
2 connect(manager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(replayFinished(QNetworkReply*)));
(2)新建QUrl类对象jsonUrl,并且将API和城市传入,请求数据:
1 QUrl jsonUrl(url + city);
2 manager->get( QNetworkRequest(jsonUrl) );
(3)请求数据完成后,得到储存有信息的QNetworkReply类对象,读取数据:
1 QVariant status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
2 if(reply->error() != QNetworkReply::NoError || status_code != 200)
3 {
4 QMessageBox::warning(this, "错误", "天气:请求数据错误,检查网络连接!", QMessageBox::Ok);
5 return;
6 }
7 QByteArray bytes = reply->readAll();
4.3 解析Json格式天气信息
如图4.2所示,Json编码格式数据[6]以“{}”包含,Qt把这整体数据叫做“JsonDocument”,相当于目录,并且提供了QJsonDocument类来将信息转换为Qt能够解析的Json数据格式,和QJsonParseError类来检测数据是不是Json格式。
1 QJsonParseError err;
2 QJsonDocument jsonDoc = QJsonDocument::fromJson(bytes, &err); // 检测json格式
3 if (err.error != QJsonParseError::NoError) // Json格式错误
4 {
5 return;
6 }
得到Json目录后,里面的内容叫做“QJsonObject”,可以由QJsonDocument的“object()”方法来获取:
1 QJsonObject jsObj = jsonDoc.object();
图4.3 Json格式数据片段
如图4.3所示Json数据片段“”date”:”20180102””,其中”date”叫做“键”,”20180102”叫做键对应的值,取得Json数据的“键”来获取对应的“值”,Qt将之叫做“value”。
依次取得日期(date)、消息(message)、城市(city)等能一步取得的信息。键“data”对应的值又是一个“QJsonObject”,取得“data”键值,再将之转换为Object:
1 QJsonObject dataObj = jsObj.value("data").toObject();
接着,依次取得dataObj包含的键值:
1 today.shidu = dataObj.value("shidu").toString();
2 today.pm25 = QString::number( dataObj.value("pm25").toDouble() );
3 today.quality = dataObj.value("quality").toString();
4 today.wendu = dataObj.value("wendu").toString() + "°";
5 today.ganmao = dataObj.value("ganmao").toString();
再看键“forecast”对应的值为数组,Qt将Json数组叫做“QJsonArray”,取得Json数组:
1 QJsonArray forecastArr = dataObj.value("forecast").toArray();
每个数组元素对应一个“QJsonObject”,依照上述方法,依次取得里面的键值即可。Qt对Json格式数据的解析以及转换如表4.1、表4.2、图4.4所示:
表4.1 Qt对Json格式数据的解析示例 数据 操作 源数据 QJsonDocument jsonDoc = QJsonDocument::fromJson(bytes, &err); Json整体数据 QJsonObject jsObj = jsonDoc.object(); 普通“值(value)” QString message = jsObj.value("message").toString(); 带对象(Object)的“值” QJsonObject dataObj = jsObj.value("data").toObject(); 带数组(Array)的“值” QJsonArray forecastArr = dataObj.value("forecast").toArray(); 数组元素为对象 QJsonObject dateObj = forecastArr.at(j).toObject();
表4.2 数据间的相互转换关系表 数据类型 转换方法 转换后数据类型 QByteArray QJsonDocument::fromJson(QByteArray, QJsonParseError) QJsonDocument QJsonDocument object() QJsonObject QJsonObject value(QString key) QJsonValue QJsonValue toObject() QJsonObject QJsonValue toArray() QJsonArray QJsonValue toString() QString QJsonValue toDouble() double double QString::number(double) QString QString toDouble() double
图4.4 数据间的相互转换关系图
为了方便管理和赋值,我们将今日天气数据和六天天气信息设计成结构体,如下:
1 struct Today
2 {
3 QString date;
4 QString wendu;
5 QString city;
6 QString shidu;
7 QString pm25;
8 QString quality;
9 QString ganmao;
10 QString fx;
11 QString fl;
12 QString type;
13 QString sunrise;
14 QString sunset;
15 QString notice;
16 };
1 struct Forecast
2 {
3 QString date;
4 QString high;
5 QString low;
6 QString aqi;
7 QString type;
8 };
定义Forecast结构体数组forecast,元素数量为6,将界面④部分的控件使用容器来管理,再使用for循环依次赋值。容器定义如下:
1 QList<QLabel *> forecast_week_list;
2 QList<QLabel *> forecast_date_list;
3 QList<QLabel *> forecast_aqi_list;
4 QList<QLabel *> forecast_type_list;
5 QList<QLabel *> forecast_typeIco_list;
6 QList<QLabel *> forecast_high_list;
7 QList<QLabel *> forecast_low_list;
4.4 绘制日出日落图案
4.4.1 Qt5事件过滤器简介
QLabel本身是有绘图事件(QPaintevent)的,然而,QWidget上的QLabel控件的绘图事件会被QWidget屏蔽,所以我们不能直接将其作为QPaintDevice来绘图。这时,需要用到事件过滤机制。
Qt提供了事件过滤器来实现在一个部件中监控其他多个部件的事件。事件过滤器与其他部件不同,它不是一个类,只是由两个函数组成的一种操作,用来完成一个部件对其他部件事件的监视。这两个函数分别是installEventFilter()和eventFilter(),都是QObject类中的函数。
那么,首先,给日出日落绘图控件(sunRiseSetLb)启用事件过滤器:
1 ui->sunRiseSetLb->installEventFilter(this);
然后,才能在eventFilter函数中进行绘图操作。
4.4.2 图案绘制
(一)绘制底线和文本
将画笔宽度设置为0.5,颜色设置为黄色(Qt::yellow),使用Qpainter类的drawLine()方法绘制底线,起始点坐标为:(20,75),终止点坐标为:(130,75)。
再将画笔字体设置为微软雅黑(Microsoft Yahei),文字大小设置为8pt,粗细设置为正常(QFont::Normal),颜色设置为白色。使用QPainter类的drawText()方法绘制文本,文本内容为日出、日落时间和“日出日落”四个字。日出时间文本绘制区域为:(0,80,50,20);日落时间绘制区域为:(100, 80, 50, 20);“日出日落”文字绘制区域为:(50, 80, 50, 20)。绘制效果如下图(图4.5)所示:
图4.5 底线和文本绘制
(二)绘制圆弧
为了达到生动形象的效果,我们以半圆来表示日出日落,半圆底线已经绘制完毕,接下来绘制弧线,弧线采用虚线方式来表现。QPainter的画笔设置虚线的方法为QPen类的setStyle()函数,画笔的各种风格如图4.6所示。
图4.6 画笔的各种风格
选择Qt::DotLine风格,画笔颜色设置为绿色(Qt::green),宽度设置为0.5,调用QPainter类的drawArc()函数来进行圆弧的绘制。Qt5帮助手册对于此函数的描述为:
void QPainter::drawArc(const QRectF &rectangle, int startAngle, int spanAngle)
Draws the arc defined by the given rectangle, startAngle and spanAngle.
The startAngle and spanAngle must be specified in 1/16th of a degree, i.e. a full circle equals 5760 (16 * 360). Positive values for the angles mean counter-clockwise while negative values mean the clockwise direction. Zero degrees is at the 3 o'clock position.
可知,第一个参数指定一个矩形(QRect),画笔会自动将圆心设置在矩形中心。第二个参数为起始角度,第三个参数为跨越角度,跨越方向为逆时针方向。需要注意的是,Qt对于绘制圆弧的角度是正常角度的1/16,也就是说,起始角度和跨越角度都需要扩大16倍。
那么,我们指定矩形区域为:(25, 25, 100, 100),起始角度为0,终止角度为180×16,绘制出来的效果如图4.7所示。
图4.7 圆弧绘制
(三)绘制日出日落占比图形
日出日落图形以扇形方式展现,从0到180度。那么需要计算当前时间对于日出日落时间的占比是多少,然后根据占比来计算起始角度和跨越角度。步骤如下:
(1)计算出日出日落时间和当前时间,忽略秒,用分钟单位来表示:
1 static QString sunsetHour = sunSetTime.at(0);
2 static QString sunsetMint = sunSetTime.at(1);
3 static QString sunriseHour = sunRiseTime.at(0);
4 static QString sunriseMint = sunRiseTime.at(1);
5 static int sunrise = sunriseHour.toInt() * 60 + sunriseMint.toInt();
6 static int sunset = sunsetHour.toInt() * 60 + sunsetMint.toInt();
7 int now = QTime::currentTime().hour() * 60 + QTime::currentTime().minute();
(2)那么,起始角度为:
1 startAngle = ( (double)(sunset - now) / (sunset - sunrise) ) * 180 * 16;
(3)跨越角度为:
2 spanAngle = ( (double)(now - sunrise) / (sunset - sunrise) ) * 180 * 16;
(4)绘制扇形:
1 painter.drawPie(rect[0], startAngle, spanAngle);
完成绘制函数后,设定一个定时器,以1秒间隔重新绘制即可。
4.5 绘制日最高、最低温曲线
同样的,曲线是在QLabel控件上绘制,同样需要事件过滤器:
1 ui->curveLb->installEventFilter(this);
(一)曲线转折点X坐标计算及扩张值设定
为了防止温度过高或过低导致曲线超出控件范围,也为了使温度曲线能够大致居中显示,防止曲线曲线过于偏上或者偏下,需要计算出六天最高温度的平均值。curveLb控件高度为105,所以给定最高温度平均值起始Y坐标为45较为合适。
为了界面的美观和曲线的平衡,将curveLb平均分成六份,每一份的中心点作为温度曲线的转折点,六个中心点的x坐标如下:
1 int pointX[6] = {35, 103, 172, 241, 310, 379};
然后是温度每升高一度,y坐标的扩张值设定。如果扩张值设定过大,那么日温差就必须小;如果扩张值设定过小,那么最高最低温曲线倾斜度不够,不能够明显表现温差。经测试,扩张值设定为3正好合适。用s来表示所能容纳的日温差,单位为度(°),那么有:
s = [(105-45) ÷ 3]° ≈ 22° (4.1)
这个值已经能满足绝大多数城市的日温差。
(二)曲线转折点Y坐标的计算
假定高温平均值为a,日最高温度为h,日最低温度为l,起始点坐标为s,扩张值为i,日最高温Y坐标为HY,日最低温Y坐标为LY。那么有:
HY = s - (h-a) × i (4.2)
LY = s + (a-l) × i (4.3)
(三)曲线绘制
取得每个转折点的X坐标和Y坐标后,即可使用QPainter类的drawLine()函数,依坐标点一段一段绘线。由于六天天气信息中,包含了昨天的天气信息,为了使曲线更为明显地突出昨天温度与未来五天的温度,故绘制曲线时将第一段线绘制为虚线(Qt::DotLine)。为了凸出显示出日最高温和日最低温度,绘制最高温度曲线时,画笔颜色设置为RGB(255, 170, 0);绘制最低温度曲线时,画笔颜色设置为RGB(0, 255, 255)。
(1)绘制高温曲线核心代码:
1 painter.save();
2 pen.setColor(QColor(255, 170, 0));
3 pen.setStyle(Qt::DotLine);
4 painter.setPen(pen);
5 painter.setBrush(QColor(255, 170, 0));
6 painter.drawEllipse(QPoint(pointX[0], pointHY[0]), ORIGIN_SIZE, ORIGIN_SIZE);
7 painter.drawEllipse(QPoint(pointX[1], pointHY[1]), ORIGIN_SIZE, ORIGIN_SIZE);
8 painter.drawLine(pointX[0], pointHY[0], pointX[1], pointHY[1]);
9 pen.setStyle(Qt::SolidLine);
10 pen.setWidth(1);
11 painter.setPen(pen);
12 for (int i = 1; i < 5; i++)
13 {
14 painter.drawEllipse(QPoint(pointX[i+1], pointHY[i+1]), ORIGIN_SIZE, ORIGIN_SIZE);
15 painter.drawLine(pointX[i], pointHY[i], pointX[i+1], pointHY[i+1]);
16 }
17 painter.restore();
(1)绘制低温曲线核心代码:
1 pen.setColor(QColor(0, 255, 255));
2 pen.setStyle(Qt::DotLine);
3 painter.setPen(pen);
4 painter.setBrush(QColor(0, 255, 255));
5 painter.drawEllipse(QPoint(pointX[0], pointLY[0]), ORIGIN_SIZE, ORIGIN_SIZE);
6 painter.drawEllipse(QPoint(pointX[1], pointLY[1]), ORIGIN_SIZE, ORIGIN_SIZE);
7 painter.drawLine(pointX[0], pointLY[0], pointX[1], pointLY[1]);
8 pen.setColor(QColor(0, 255, 255));
9 pen.setStyle(Qt::SolidLine);
10 painter.setPen(pen);
11 for (int i = 1; i < 5; i++)
12 {
13 painter.drawEllipse(QPoint(pointX[i+1], pointLY[i+1]), ORIGIN_SIZE, ORIGIN_SIZE);
14 painter.drawLine(pointX[i], pointLY[i], pointX[i+1], pointLY[i+1]);
15 }
(2)效果如下图(图4.8)所示:
图4.8 温度曲线效果图
五 代码下载地址
http://download.csdn.net/download/wu9797/10213010