Qt之天气预报——功能实现篇(含源码+注释)
创始人
2024-04-19 12:44:50
0

文章目录

  • 一、功能概述
    • 1.基本功能
    • 2.实时天气模式
    • 3.预报天气模式
  • 二、天气预报功能示例图
    • 1.城市选择(下拉框)
    • 2.城市选择(文本框)
    • 3. 预报天气日期切换
    • 4.刷新操作
  • 三、使用类的简述
    • 3.1 涉及的Qt类
    • 3.2 自定义类
      • 3.2.1 自定义结构体
      • 3.2.2 自定义类
  • 四、配置文件
    • 1.API链接配置文件——WeatherApi.xml
    • 1.城市编码配置文件——CityCodeInfo.xml
  • 五、遇到的问题
    • 1.图表坐标轴不显示
    • 2.时间轴数据索引位置对应
  • 六、源码
    • 6.1 结构体头文件
    • 6.2 预报天气按钮类
    • 6.3 预报天气模块类
    • 6.4 天气预报主类
  • 总结
  • 相关文章
    • 天气预报系列
    • 相关类的基本使用

一、功能概述

天气预报包含实时天气模式和预报天气模式。

1.基本功能

添加右键菜单;
可切换天气模式;
显示天气报告时间;
刷新功能(右键菜单);
城市选择模式:包括下拉框选择和文本框输入(右键菜单);
切换城市更新天气预报信息,显示报告时间、城市、温度等常用信息。

2.实时天气模式

实时天气模式功能比较单一,仅显示当天的天气基本信息。

3.预报天气模式

预报天气包含四天的天气信息(包括当天天气),默认显示当天天气信息;可通过自定义按钮对象切换天气信息,且预报天气下方为预报日期日、夜间温度的折线图。

二、天气预报功能示例图

1.城市选择(下拉框)

下图通过下拉列表框切换城市从而自动查询天气,其中包括实时天气和预报天气。
在这里插入图片描述

2.城市选择(文本框)

下图演示文本框的联想功能,以及通过城市编码获取天气操作。
在这里插入图片描述

3. 预报天气日期切换

下图通过预报天气中的星期模块切换对应日期的信息。
在这里插入图片描述

4.刷新操作

下图通过右键菜单刷新按钮重新获取天气信息,肉眼可见图表的刷新。
在这里插入图片描述
提示:不会使用Qt设计师设计界面的小伙伴点击这里

三、使用类的简述

3.1 涉及的Qt类

QFile:读取配置文件。
QDateTime:转换时间串。
QCompleter:文本框联想类。
QMessageBox:提示对话框。
QDomDocument、QDomElement:解析XML配置文件;须在pro文件添加“QT += xml”。
QJsonDocument、QJsonObject、QJsonArray:解析天气预报返回的Json串。
QChartView、QChart、QLineSeries、QDateTimeAxis、QValueAxis:绘制折线图;须在pro文件添加“QT += charts”,并在图表使用类中添加“QT_CHARTS_USE_NAMESPACE”使用命名空间。
QNetworkAccessManager、QNetworkReply、QNetworkRequest:天气预报信息申请和获取返回的Json串。
注:简述仅代表类在本文中的作用。

3.2 自定义类

3.2.1 自定义结构体

stApiInfo:存储API链接信息结构体;可以生成链接、设置链接和判断链接信息释放包含空。
stLiveInfo:存储实时信息结构体;可设置实时信息和判断实时信息是否包含空。
stForecastsInfo:存储预报信息结构体;可设置单个预报天气信息内容。
stWeatherInfo:存储天气信息结构体;包含基础信息、实时信息结构体和预报信息结构体链表。

3.2.2 自定义类

CWeatherForecast:天气预报类;其中包含API信息结构体、天气信息结构体、城市信息等。
CForecastWidget:预报天气信息显示类;主要显示预报天气信息和切换预报天气。
CForecastBtn:预报天气天气切换按钮类;响应鼠标点击发送索引值,从而切换天气,原型为QWidget,但是主要响应点击信号,所以在此称之为按钮。

四、配置文件

1.API链接配置文件——WeatherApi.xml

配置文件内容可自定义,API_KEY的申请详情可查看Qt之天气预报实现(一)预备篇。




1.城市编码配置文件——CityCodeInfo.xml

城市编码可通过Web服务 API 相关下载下载。
注:因城市编码信息过多,在此仅展示部分。


......

五、遇到的问题

1.图表坐标轴不显示

通过序列对象的attachAxis()函数将序列对象与X/Y坐标轴对象关联起来即可。

2.时间轴数据索引位置对应

时间轴起始值为1970/01/01,当我设置时间轴范围时,需要将获取日期时间值的毫秒值,然后将毫秒值和温度值添加到序列中即可(详情请见源码)。

六、源码

6.1 结构体头文件

StWeatherForecast.h

#ifndef STWEATHERFROECAST_H
#define STWEATHERFROECAST_H#include 
#include 
#include // 定义api信息结构体
typedef struct stApiInfo
{QString link;   // 链接QString apiKey; // API KeyQString cityCode;   // 城市编码QString extensions; // 天气查看类型// api信息结构体初始化stApiInfo(){link = "http://restapi.amap.com/v3/weather/weatherInfo?";   // 链接apiKey = "自己的APIKEY";    // apiKeycityCode = "110000";    // 城市码extensions = "base";    // 天气查看类型(实时、预报)}/*** @brief joinApiLink 拼接API链接* @return 返回拼接好的API链接*/QString joinApiLink(){QString ret = "";// 拼接API_KEYret = link + "key=" + apiKey;ret += "&city=" + cityCode;ret += "&extensions=" + extensions;return ret;}/*** @brief fieldIsEmpty 判断是否有字段值为空* @return 是否为空*/bool fieldIsEmpty(){return link.isEmpty() || apiKey.isEmpty() || cityCode.isEmpty() || extensions.isEmpty();}
}stApiInfo;// 实时天气信息结构体
typedef struct stLiveInfo
{QString weather;        // 天气QString temperature;    // 温度QString winddirection;  // 风向QString windpower;      // 风力等级QString humidity;       // 湿度// 无参构造stLiveInfo(){}// 使用构造函数使其将数据传入,通过初始化列表赋值stLiveInfo(const QString &weather, const QString &temperature, const QString &winddirection, const QString &windpower, const QString &humidity): weather(weather), temperature(temperature), winddirection(winddirection), windpower(windpower), humidity(humidity){}/*** @brief setWeatherInfo 设置天气信息* @param weather 天气* @param temperature 温度* @param winddirection 风向* @param windpower 风力等级* @param humidity 湿度*/void setWeatherInfo(const QString &weather, const QString &temperature, const QString &winddirection, const QString &windpower, const QString &humidity){this->weather = weather;this->temperature = temperature;this->winddirection = winddirection;this->windpower = windpower;this->humidity = humidity;}/*** @brief fieldIsEmpty 判断是否有字段值为空* @return 是否为空*/bool fieldIsEmpty(){return weather.isEmpty() || temperature.isEmpty()|| winddirection.isEmpty() || windpower.isEmpty() || humidity.isEmpty();}
}stLiveInfo;// 预报天气信息结构体
typedef struct stForecastsInfo
{
private:QHash weekHash;public:QString date;           // 日期QString week;           // 星期QString dayweather;     // 日间天气QString nightweather;   // 夜间天气QString daytemp;        // 日间温度QString nighttemp;      // 夜间温度QString daywind;        // 日间风向QString nightwind;      // 夜间风向QString daypower;       // 日间风力等级QString nightpower;     // 夜间风力等级// 创建无参构造stForecastsInfo(){//! 初始化Hash的值,为方便获取周数据//! 此次可通过配置文件读取,不过我在这里就偷一下懒了,hhhweekHash[1] = "周一";weekHash[2] = "周二";weekHash[3] = "周三";weekHash[4] = "周四";weekHash[5] = "周五";weekHash[6] = "周六";weekHash[7] = "周日";}/*** @brief setDate 设置日期(自动设置星期)* @param dateStr 日期字符串*/void setDate(const QString &dateStr){// 结构体日期变量赋值this->date = dateStr;// 将日期字符串转为QDate对象(必须设置字符串的日期格式哦,否则识别不到日期)QDate date = QDate::fromString(dateStr, "yyyy-MM-dd");// 结构体星期变量赋值this->week = weekHash[date.dayOfWeek()];}/*** @brief setWeather 设置日/夜间天气* @param dayweather 日间天气* @param nightweather 夜间天气*/void setWeather(const QString &dayweather, const QString &nightweather){this->dayweather = dayweather;this->nightweather = nightweather;}/*** @brief setTemperature 设置日/夜间温度* @param daytemp 日间温度* @param nighttemp 夜间温度*/void setTemperature(const QString &daytemp, const QString &nighttemp){this->daytemp = daytemp;this->nighttemp = nighttemp;}/*** @brief setWindDirection 设置日/夜间风向* @param daywind 日间风向* @param nightwind 夜间风向*/void setWindDirection(const QString &daywind, const QString &nightwind){this->daywind = daywind;this->nightwind = nightwind;}/*** @brief setWindPower 设置日/夜间风力等级* @param daypower 日间风力等级* @param nightpower 夜间风力等级*/void setWindPower(const QString &daypower, const QString &nightpower){this->daypower = daypower;this->nightpower = nightpower;}
}stForecastsInfo;// 天气信息结构体
typedef struct stWeatherInfo
{// 基本信息(实时、预报天气都有以下属性)QString province;           // 省份QString city;               // 城市QString adcode;             // 区域编码QString reporttime;         // 报告时间// 因为实时信息和预报信息内容不同,需分开存储stLiveInfo              stliveInfo;         // 实时天气信息// 实时信息包含多天天气,需用容器存储QList  stForecastInfoList; // 预报天气信息/*** @brief setBaseInfo 设置基础信息函数* @param province  省份* @param city 成都* @param adcode 区域编码* @param reportTime 报告时间*/void setBaseInfo(const QString &province, const QString &city, const QString &adcode, const QString &reportTime){this->province = province;this->city = city;this->adcode = adcode;this->reporttime = reportTime;}}stWeatherInfo;#endif // STWEATHERFROECAST_H

6.2 预报天气按钮类

注:因其功能主要为点击,所以称为按钮类。
CForecastBtn.h

#ifndef CFORECASTBTN_H
#define CFORECASTBTN_H#include namespace Ui {
class CForecastBtn;
}class CForecastBtn : public QWidget
{Q_OBJECTpublic:explicit CForecastBtn(QWidget *parent = nullptr);~CForecastBtn();/*** @brief setBtnInfo 设置按钮显示信息* @param week 周几* @param weather 天气* @param dayTemp 日间温度* @param nightTemp 夜间温度*/void setBtnInfo(QString week, QString weather, QString dayTemp, QString nightTemp);
signals:/*** @brief clicked 点击信号*/void clicked();private:Ui::CForecastBtn *ui;// QWidget interface
protected:/*** @brief mouseReleaseEvent 鼠标释放时间* @param event 事件对象*/void mouseReleaseEvent(QMouseEvent *event);
};#endif // CFORECASTBTN_H

CForecastBtn.cpp

#include "CForecastBtn.h"
#include "ui_CForecastBtn.h"#include CForecastBtn::CForecastBtn(QWidget *parent) :QWidget(parent),ui(new Ui::CForecastBtn)
{ui->setupUi(this);
}CForecastBtn::~CForecastBtn()
{delete ui;
}void CForecastBtn::setBtnInfo(QString week, QString weather, QString dayTemp, QString nightTemp)
{// 设置周标签ui->weekLab->setText(week);// 设置天气ui->weatherLab->setText(weather);// 设置温度标签ui->tempLab->setText(nightTemp + "°~" + dayTemp + "°");
}void CForecastBtn::mouseReleaseEvent(QMouseEvent *event)
{// 当前为左键按钮时进入if(Qt::LeftButton == event->button()){// 发出点击信号emit clicked();}
}

CForecastBtn.ui


CForecastBtn007266Form星期天气0°C~0°C

6.3 预报天气模块类

CForecastWidget.h

#ifndef CFORECASTWIDGET_H
#define CFORECASTWIDGET_H#include "StWeatherForecast.h"
#include 
#include 
#include 
#include 
#include QT_CHARTS_USE_NAMESPACE // 使用chart的命名空间namespace Ui {
class CForecastWidget;
}class CForecastWidget : public QWidget
{Q_OBJECT
public:explicit CForecastWidget(QWidget *parent = nullptr);~CForecastWidget();/*** @brief initialize 初始化类(使构造函数不做过多的操作)* @return 初始化标志值(通过该值区分是否初始化成功或初始化到哪一步)*/int initialize();/*** @brief unInitialize 初始化类(使析构函数不做过多的操作)* @return 反初始化标志值(通过该值区分是否释放成功或释放到哪一步)*/int unInitialize();/*** @brief updateWeatherInfo 更新按钮和图表信息* @param infoList 预报天气结构体联邦*/void updateWeatherInfo(const QList &infoList);/*** @brief releaseBtn 释放自定义按钮对象*/void releaseBtn();signals:/*** @brief forecastBtnClickedSig 转发按钮点击信号* @param index 点击按钮索引*/void forecastBtnClickedSig(int index);public slots:private:Ui::CForecastWidget     *ui;QChart                  *m_lineChart;   // 折线图的chartQLineSeries             *m_daySeries;   // 日间温度序列QLineSeries             *m_nightSeries; // 夜间温度序列QDateTimeAxis           *m_dateXAxis;   // 折线图的X轴QValueAxis              *m_valueYAxis;  // Y轴对象};#endif // CFORECASTWIDGET_H

CForecastWidget.cpp

#include "CForecastWidget.h"
#include "ui_CForecastWidget.h"
#include "CForecastBtn.h"#include CForecastWidget::CForecastWidget(QWidget *parent): QWidget(parent), ui(nullptr), m_lineChart(nullptr), m_daySeries(nullptr), m_nightSeries(nullptr), m_dateXAxis(nullptr), m_valueYAxis(nullptr)
{}CForecastWidget::~CForecastWidget()
{}int CForecastWidget::initialize()
{// 返回值对象int ret = 0;if(nullptr == ui){ui = new Ui::CForecastWidget;ui->setupUi(this);ret |= 0x1;}// 创建qchart对象if(nullptr == m_lineChart){// 创建图表对象m_lineChart = new QChart;// 将图例对象放到图表的左边m_lineChart->legend()->setAlignment(Qt::AlignLeft);// 创建默认坐标轴m_lineChart->createDefaultAxes();ret |= 0x10;}// 创建日间序列对象if(nullptr == m_daySeries){m_daySeries = new QLineSeries;// 设置序列名m_daySeries->setName("日间温度");ret |= 0x100;}// 创建夜间序列对象if(nullptr == m_nightSeries){m_nightSeries = new QLineSeries;// 设置序列名m_nightSeries->setName("夜间温度");ret |= 0x1000;}// 创建时间(X)轴对象if(nullptr == m_dateXAxis){m_dateXAxis = new QDateTimeAxis;// 设置坐标轴标签显示格式m_dateXAxis->setFormat("yyyy/MM/dd");ret |= 0x10000;}// 创建Y轴对象if(nullptr == m_valueYAxis){m_valueYAxis = new QValueAxis;ret |= 0x100000;}// 通过返回值对象判断所有对象是否初始化完成,然后做关联设置操作if(0x111111 == ret){// 将序列对象添加到chart对象中并将chart对象添加到ui中的GraphiView对象中m_lineChart->addSeries(m_daySeries);m_lineChart->addSeries(m_nightSeries);// 添加X、Y轴对象到图表中m_lineChart->addAxis(m_dateXAxis, Qt::AlignBottom);m_lineChart->addAxis(m_valueYAxis, Qt::AlignLeft);//! 序列对象关联X、Y坐标轴// 关联X轴m_daySeries->attachAxis(m_dateXAxis);m_nightSeries->attachAxis(m_dateXAxis);// 关联Y轴m_daySeries->attachAxis(m_valueYAxis);m_nightSeries->attachAxis(m_valueYAxis);// 将图表对象添加到ui中ui->weatherChartView->setChart(m_lineChart);}return ret;
}int CForecastWidget::unInitialize()
{// 返回值对象int ret = 0;// 释放自定义按钮对象releaseBtn();// 释放Y轴if(nullptr != m_valueYAxis){delete m_valueYAxis;m_valueYAxis = nullptr;ret |= 0x1;}// 释放X轴if(nullptr != m_dateXAxis){delete m_dateXAxis;m_dateXAxis = nullptr;ret |= 0x10;}// 释放夜间序列对象if(nullptr != m_nightSeries){delete m_nightSeries;m_nightSeries = nullptr;ret |= 0x100;}// 释放日间序列对象if(nullptr != m_daySeries){delete m_daySeries;m_daySeries = nullptr;ret |= 0x1000;}// 释放qchart对象if(nullptr != m_lineChart){delete m_lineChart;m_lineChart = nullptr;ret |= 0x10000;}// 释放Ui对象if(nullptr != ui){delete  ui;ui = nullptr;ret |= 0x100000;}return ret;
}void CForecastWidget::updateWeatherInfo(const QList &infoList)
{// 释放前一次设置的按钮对象releaseBtn();// 清空日间、夜间序列内容m_daySeries->clear();m_nightSeries->clear();// 获取温度最值,方便设置图表Y轴的最值int maxTemp = infoList.first().daytemp.toInt(); // 从日间温度取最大值int minTemp = infoList.first().nighttemp.toInt(); //从夜间温度取最小值// 设置时间坐标轴日期范围m_dateXAxis->setRange(QDateTime::fromString(infoList.first().date, "yyyy-MM-d"), QDateTime::fromString(infoList.last().date, "yyyy-MM-dd"));// 遍历预报天气结构体信息容器for(int index = 0; index != infoList.size(); ++index){// 获取当前日期信息对象const stForecastsInfo &info = infoList.at(index);// 创建按钮对象CForecastBtn *btn = new CForecastBtn;// 将按钮添加到布局器中ui->forecastBtnLayout->addWidget(btn);// 设置按钮对按钮对象的显示信息btn->setBtnInfo(info.week, info.dayweather, info.daytemp, info.nighttemp);// 连接按钮信号信号槽connect(btn, &CForecastBtn::clicked, [=](){// 转发点击信号emit forecastBtnClickedSig(index);});// 获取日/夜间温度int dayTemp = info.daytemp.toInt();int nightTemp = info.nighttemp.toInt();//! 时间轴对象需要将时间转换为毫秒来定位索引qint64 timeIndex = QDateTime::fromString(infoList[index].date, "yyyy-MM-d").toMSecsSinceEpoch();// 将日/夜间温度追加到对应的序列对象中m_daySeries->append(timeIndex, dayTemp);m_nightSeries->append(timeIndex, nightTemp);// 通过三目运算符比较当前日/夜间温度,并取出较大/小值maxTemp = dayTemp > maxTemp ? dayTemp : maxTemp;minTemp = nightTemp < minTemp ? nightTemp : minTemp;}// 设置Y轴的值范围, 并在最大/小值加/减2使图像不那么靠近边界m_valueYAxis->setRange(minTemp - 2, maxTemp + 2);
}void CForecastWidget::releaseBtn()
{// 定义布局器item对象QLayoutItem *child;// 循环获取布局器item对象,并判其不为空while(nullptr != (child = ui->forecastBtnLayout->takeAt(0))){// 获取item对象中的widgetQWidget *wgt = child->widget();// 判断widget是否为空if(nullptr != wgt){// 释放widget空间delete wgt;}// 释放widget空间delete child;}
}

CForecastWidget.ui


CForecastWidget00400300Form00000QChartViewQGraphicsView
qchartview.h

6.4 天气预报主类

CWeatherForecast.h

#ifndef CWEATHERFORECAST_H
#define CWEATHERFORECAST_H#include "StWeatherForecast.h"
#include "CForecastWidget.h"#include 
#include 
#include     // 导入请求/接受天气查询API的类
#include namespace Ui {
class CWeatherForecast;
}class CWeatherForecast : public QMainWindow
{Q_OBJECTpublic:explicit CWeatherForecast(QWidget *parent = nullptr);~CWeatherForecast();/*** @brief initialize 初始化类(使构造函数不做过多的操作)* @return 初始化标志值(通过该值区分是否初始化成功或初始化到哪一步)*/int initialize();/*** @brief unInitialize 初始化类(使析构函数不做过多的操作)* @return 反初始化标志值(通过该值区分是否释放成功或释放到哪一步)*/int unInitialize();/*** @brief loadConfig 加载配置文件* @param path 指定文件路径* @return 是否加载成功*/bool loadConfig(QString path);/*** @brief loadWeatherForecastCfg 加载天气预报配置文件* @param path 文件路径* @return 是否加载成功*/bool loadWeatherForecastCfg(QString path);/*** @brief readCityInfo 读取城市信息* @param root root节点* @return 是否读取成功*/bool readCityInfo(const QDomElement &root);/*** @brief readAPiInfo 读取API信息* @param root root节点* @return 是否读取成功*/bool readAPiInfo(const QDomElement &root);private:/*** @brief parseJson json语句转换槽函数* @param jsonArray json语句容器*/int parseJson(QByteArray jsonData);/*** @brief parseLiveJson 解析实时天气信息* @param weatherInfo 实时天气信息对象*/void parseLiveJson(QJsonObject weatherInfo);/*** @brief parseForecastJson 解析预报天气信息* @param weatherInfo 预报天气信息对象*/void parseForecastJson(QJsonObject weatherInfo);/*** @brief loadUiInfo 根据标记值加载ui* @param isChecked 标记值*/void loadUiInfo(bool isChecked);/*** @brief loadForeCastInfo 加载指定预报信息结构体的内容* @param info 信息结构体*/void loadForeCastInfo(const stForecastsInfo &info);/*** @brief judgeCityEditText 判断城市编辑栏的文本信息* @param text 存放文本框信息对应的城市编码* @return 文本是否正确, 若是更新文本框信息不正确则不修改编码存储容器传入时的值*/bool judgeCityEditText(QString &text);/*** @brief sendWeatherRequest 发送天气信息请求函数*/void sendWeatherInfoRequest();/*** @brief responseMenuAction 响应右键菜单* @param action 右键菜单点击对象*/void responseMenuAction(QAction *action);private slots:/*** @brief on_recvNetworkReply 接收API返回数据对象槽函数* @param reply 包含API数据的对象*/void on_recvNetworkReply(QNetworkReply *reply);/*** @brief on_switchModeBtn_clicked 模式切换按钮槽函数* @param checked 当前按钮选中状态*/void on_switchModeBtn_clicked(bool checked);/*** @brief on_forecastBtnClicked 预报天气界面按钮点击槽函数* @param index 点击按钮的索引*/void on_forecastBtnClicked(int index);/*** @brief on_provinceComboBox_currentIndexChanged 省份下拉列表框改变槽函数* @param arg1 改变的文本*/void on_provinceComboBox_currentIndexChanged(const QString &arg1);/*** @brief on_cityComboBox_currentIndexChanged 城市下拉列表框改变槽函数* @param arg1 改变的文本*/void on_cityComboBox_currentIndexChanged(const QString &arg1);/*** @brief on_countyComboBox_currentIndexChanged 区县下拉列表框改变槽函数* @param arg1 改变的文本*/void on_countyComboBox_currentIndexChanged(const QString &arg1);/*** @brief on_customContextMenuRequested 定制菜单信号槽函数*/void on_customContextMenuRequested();/*** @brief on_cityEdit_textChanged 城市编辑框文本改变槽函数* @param arg1 改变的文本*/void on_cityEdit_textChanged(const QString &arg1);private:Ui::CWeatherForecast                        *ui;QNetworkAccessManager                       *m_networkAccMgr;       // 访问天气查询API的网络对象stApiInfo                                   m_stApiInfo;            // api信息结构体对象stWeatherInfo                               m_stWeatherInfo;        // 天气信息结构体CForecastWidget                             *m_forecastWgt;         // 预报天气信息图表显示板块QMap>   m_cityInfoMap;          // 城市信息容器 <省份,<城市,区县>>QMap                      m_codeInfoMap;          // 编码信息容器 <城市,编码>QCompleter                                  *m_completer;           // 过滤器对象};#endif // CWEATHERFORECAST_H

CWeatherForecast.cpp

#include "CWeatherForecast.h"
#include "ui_CWeatherForecast.h"#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include CWeatherForecast::CWeatherForecast(QWidget *parent): QMainWindow(parent), ui(nullptr), m_networkAccMgr(nullptr), m_forecastWgt(nullptr), m_completer(nullptr)
{
}CWeatherForecast::~CWeatherForecast()
{
}int CWeatherForecast::initialize()
{// 定义返回对象int ret = 0;// 初始化uiif(nullptr == ui){ui = new Ui::CWeatherForecast;ui->setupUi(this);// 设置右键菜单信号发送this->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);connect(this, &CWeatherForecast::customContextMenuRequested, this, &CWeatherForecast::on_customContextMenuRequested);ret |= 0x1;}// 初始化网络访问对象if(nullptr == m_networkAccMgr){// 创建网络访问对象空间m_networkAccMgr = new QNetworkAccessManager(this);// 连接接受网络返回的信号槽connect(m_networkAccMgr, SIGNAL(finished(QNetworkReply *)), this, SLOT(on_recvNetworkReply(QNetworkReply *)));ret |= 0x10;}// 初始化预报信息图表模块if(nullptr == m_forecastWgt){m_forecastWgt = new CForecastWidget;m_forecastWgt->initialize();// 链接信号槽connect(m_forecastWgt, &CForecastWidget::forecastBtnClickedSig, [=](int index){loadForeCastInfo(m_stWeatherInfo.stForecastInfoList.at(index));});ret |= 0x100;}// 创建过滤器对象if(nullptr == m_completer){m_completer = new QCompleter;ret |= 0x1000;}// 加载城市信息配置文件QString fileName = "./Config/CityCodeInfo.xml";if(!loadConfig(fileName)){QMessageBox::information(this, "提示", fileName + ":文件加载失败");}// 加载天气预报配置文件fileName = "./Config/WeatherApi.xml";if(!loadConfig(fileName)){QMessageBox::information(this, "提示", fileName + ":文件加载失败");}// 通过返回值对象判断所有对象是否初始化完成,然后做关联设置操作if(0x1111 == ret){// 隐藏城市编辑框ui->cityEdit->hide();// 将预报天气信息控件添加到ui中ui->forecastLayout->addWidget(m_forecastWgt);ui->forecastLayout->setStretch(0, 3);ui->forecastLayout->setStretch(1, 5);// 创建提示信息列表容器,并添加城市名称信息QStringList listTemp = m_codeInfoMap.keys();// 追加城市编码信息listTemp.append(m_codeInfoMap.values());// 创建提示信息存储的数据模板,并指定父对象QStringListModel *model = new QStringListModel(m_completer);// 将提示信息设置到模板中model->setStringList(listTemp);// 将模板设置到过滤器对象中m_completer->setModel(model);// 将过滤器对象设置到城市编辑栏中ui->cityEdit->setCompleter(m_completer);// 设置省份下拉框ui->provinceComboBox->addItems(m_cityInfoMap.keys());}return ret;
}int CWeatherForecast::unInitialize()
{// 定义返回对象int ret = 0;// 释放过滤器对象if(nullptr != m_completer){delete m_completer;m_completer = nullptr;ret |= 0x1;}// 释放预报信息对象if(nullptr != m_forecastWgt){m_forecastWgt->unInitialize();delete m_forecastWgt;m_forecastWgt = nullptr;  ret |= 0x10;}// 释放API访问对象if(nullptr != m_networkAccMgr){delete m_networkAccMgr;m_networkAccMgr = nullptr;ret |= 0x100;}// 释放uiif(nullptr != ui){delete ui;ui = nullptr;ret |= 0x1000;}return ret;
}bool CWeatherForecast::loadWeatherForecastCfg(QString path)
{bool ret = false;do{// 指定文件并打开QFile file(path);if(!file.open(QIODevice::ReadOnly)){QMessageBox::information(this, "提示", path + ":文件打开失败");ret = false;break;}// 创建QDomDocument对象并设置文档类型名QDomDocument domDoc;// 设置domDoc的上下文if(!domDoc.setContent(&file)){// 上下文设置失败,关闭QFile对象打开的文件file.close();QMessageBox::information(this, "提示", path + ":QDomDocument对象上下文设置失败");ret = false;break;}// 上下文设置成功不再使用QFile对象打开的文件,将其关闭file.close();// 从QDomDocument对象中取到对应的顶级节点元素对象QDomElement apiInfo = domDoc.firstChildElement("Root");// 判断顶级节点元素对象是否为空if(apiInfo.isNull()){QMessageBox::information(this, "提示", path + ":ApiInfo节点元素对象获取失败");ret = false;break;}// 一次获取指定的apiInfo子节点,并且获取其值m_stApiInfo.link = apiInfo.firstChildElement("Link").attribute("Link");m_stApiInfo.apiKey = apiInfo.firstChildElement("ApiKey").attribute("ApiKey");m_stApiInfo.cityCode = apiInfo.firstChildElement("CityCode").attribute("CityCode");m_stApiInfo.extensions = apiInfo.firstChildElement("Extensions").attribute("Extensions");// 返回值变为trueret = true;}while(false);return ret;
}bool CWeatherForecast::readCityInfo(const QDomElement &root)
{// 创建省份名对象QString province;// 创建城市名对象QString city;// 获取首个城市信息节点QDomElement element = root.firstChildElement("CityInfo");// 遍历root节点中的城市信息子节点while(!element.isNull()){// 获取城市名和城市编码QString name = element.attribute("Name");QString code = element.attribute("Code");// 判断城市名和城市编码不为空if(code.endsWith("0000")){// 省份对象赋值province = name;// 将省份编码信息填入容器m_codeInfoMap[province] = code;}else if(code.endsWith("00")){// 城市对象赋值city = name;}else{// 区县值添加m_cityInfoMap[province][city].append(name);// 添加区县编码信息code = code.endsWith("01") ? code.replace(code.size() - 2, 2, "00") : code;m_codeInfoMap[name] = code;}// 获取下一个信息子节点element = element.nextSiblingElement();}// 判断容器是否为空并返回return !m_cityInfoMap.isEmpty() && !m_codeInfoMap.isEmpty();
}bool CWeatherForecast::loadConfig(QString path)
{bool ret = false;do{// 指定文件并打开QFile file(path);if(!file.open(QIODevice::ReadOnly)){QMessageBox::information(this, "提示", path + ":文件打开失败");ret = false;break;}// 创建QDomDocument对象并设置文档类型名QDomDocument domDoc;// 设置domDoc的上下文if(!domDoc.setContent(&file)){// 上下文设置失败,关闭QFile对象打开的文件file.close();QMessageBox::information(this, "提示", path + ":QDomDocument对象上下文设置失败");ret = false;break;}// 上下文设置成功不再使用QFile对象打开的文件,将其关闭file.close();// 从QDomDocument对象中取到对应的顶级节点元素对象QDomElement root = domDoc.firstChildElement("Root");// 判断顶级节点元素对象是否为空if(root.isNull()){QMessageBox::information(this, "提示", path + ":root节点元素对象获取失败");ret = false;break;}QString type = root.attribute("Type");if(0 == type.compare("CityInfo")){// 获取返回值ret = readCityInfo(root);}else if(0 == type.compare("ApiInfo")){// 获取返回值ret = readAPiInfo(root);}}while(false);return ret;
}bool CWeatherForecast::readAPiInfo(const QDomElement &root)
{// 一次获取指定的apiInfo子节点,并且获取其值m_stApiInfo.link = root.firstChildElement("Link").attribute("Link");m_stApiInfo.apiKey = root.firstChildElement("ApiKey").attribute("ApiKey");m_stApiInfo.cityCode = root.firstChildElement("CityCode").attribute("CityCode");m_stApiInfo.extensions = root.firstChildElement("Extensions").attribute("Extensions");// 判断节点值是否存在空值return !m_stApiInfo.fieldIsEmpty();
}int CWeatherForecast::parseJson(QByteArray jsonData)
{// 创建返回值对象int ret = 0;//! 创建QJsonParseError对象,用于判断Json是否正确//! 尽管从API中返回的Json基本正确,但是流程还是要走,养成好习惯QJsonParseError jsonError;// 将json数据和jsonError对象传入fromJson函数中QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &jsonError);// 判断错误码是否不等于QJsonParseError::NoError,不等于则返回if(jsonError.error != QJsonParseError::NoError){return ret;}// 创建QJsonObject对象接收object的返回值QJsonObject jsonObj = jsonDoc.object();//! 判断接收的Json串是否正确//! status为1那么状态为成功,infocode为10000则返回状态为正确if("1" != jsonObj.value("status").toString() || "10000" != jsonObj.value("infocode").toString()){QMessageBox::warning(nullptr, "警告", "数据返回失败/错误");return ret;}// 获取当前模式按钮的选中状态bool isChecked = ui->switchModeBtn->isChecked();// 通过模式切换按钮判断获取当前模式QString infoType = isChecked ? "forecasts" : "lives";//! 通过infoType获取对应的内容//! 因为infoType下的标识符为‘[’所以先转换为数组//! 然后取数组下第一个元素并将其转为QJsonObject对象QJsonObject weatherInfo = jsonObj.value(infoType).toArray().at(0).toObject();// 读取基础信息(省份、城市、区域编码、报告时间)m_stWeatherInfo.setBaseInfo(weatherInfo.value("province").toString(), weatherInfo.value("city").toString(), weatherInfo.value("adcode").toString(), weatherInfo.value("reporttime").toString());// 通过按钮选中状态使用不同模式的转换函数if(isChecked){// 预报天气转换parseForecastJson(weatherInfo);}else{// 实时天气转换parseLiveJson(weatherInfo);}return ret;
}void CWeatherForecast::parseLiveJson(QJsonObject weatherInfo)
{// 获取天气QString weather = weatherInfo.value("weather").toString();// 获取温度QString temperature = weatherInfo.value("temperature").toString();// 获取风向QString winddirection = weatherInfo.value("winddirection").toString();// 获取风力QString windpower = weatherInfo.value("windpower").toString();// 获取湿度QString humidity = weatherInfo.value("humidity").toString();// 设置信息m_stWeatherInfo.stliveInfo.setWeatherInfo(weather, temperature, winddirection, windpower, humidity);if(m_stWeatherInfo.stliveInfo.fieldIsEmpty()){QMessageBox::information(this, "提示", "没有新的天气信息");return;}loadUiInfo(false);
}void CWeatherForecast::parseForecastJson(QJsonObject weatherInfo)
{// 每次添加将上次的预报天气信息清空m_stWeatherInfo.stForecastInfoList.clear();// 获取cast的内容,并将其转为数组QJsonArray forecastArray = weatherInfo.value("casts").toArray();// 获取json数组对象的迭代器QJsonArray::const_iterator iterator = forecastArray.begin();// 判断获取的起始迭代器不等于结束迭代器if(forecastArray.end() == iterator){QMessageBox::information(this, "提示", "没有新的天气信息");return;}// 遍历天气信息for(; iterator != forecastArray.end(); ++iterator){// 先追加一个预报结构体m_stWeatherInfo.stForecastInfoList.append(stForecastsInfo());// 获取最新追加结构体的引用对象stForecastsInfo &forecastsInfo = m_stWeatherInfo.stForecastInfoList.last();// 获取当前内容的QJsonObject对象QJsonObject forecastsInfoObj = (*iterator).toObject();// 设置日期forecastsInfo.setDate(forecastsInfoObj.value("date").toString());// 设置天气forecastsInfo.setWeather(forecastsInfoObj.value("dayweather").toString(), forecastsInfoObj.value("nightweather").toString());// 设置温度forecastsInfo.setTemperature(forecastsInfoObj.value("daytemp").toString(), forecastsInfoObj.value("nighttemp").toString());// 设置风向forecastsInfo.setWindDirection(forecastsInfoObj.value("daywind").toString(), forecastsInfoObj.value("nightwind").toString());// 设置风力forecastsInfo.setWindPower(forecastsInfoObj.value("daypower").toString(), forecastsInfoObj.value("nightpower").toString());}//加载ui信息loadUiInfo(true);
}void CWeatherForecast::loadUiInfo(bool isChecked)
{// 设置通用属性ui->reportTimeLab->setText(m_stWeatherInfo.reporttime);// 城市字符串 省份+城市QString cityStr = m_stWeatherInfo.province + "-" +m_stWeatherInfo.city;// 设置预报天气信息if(isChecked){// 设置预报天气城市ui->forecastWeatherCityLab->setText(cityStr);// 更新预报图表模块信息m_forecastWgt->updateWeatherInfo(m_stWeatherInfo.stForecastInfoList);// 加载当前显示的天气信息loadForeCastInfo(m_stWeatherInfo.stForecastInfoList.first());}// 设置实时天气信息else{// 设置实时天气城市ui->liveWeatherCityLab->setText(cityStr);// 获取实时信息的引用const stLiveInfo &info = m_stWeatherInfo.stliveInfo;// 设置实时信息ui->liveTempLab->setText(info.temperature + "°C");ui->liveWeatherLab->setText(info.weather);ui->liveHumidityLab->setText(info.humidity);ui->liveLindPowerLab->setText(info.windpower);ui->liveWindDirectionLab->setText(info.winddirection);}
}void CWeatherForecast::loadForeCastInfo(const stForecastsInfo &info)
{// 将ui上对应的信息设置ui->forecastWeatherLab->setText(info.dayweather);ui->forecastHighTempLab->setText(info.daytemp + "°C");ui->forecastLowTempLab->setText(info.nighttemp + "°C");ui->forecastWindPowerLab->setText(info.daypower);ui->forecastWindDirectionLab->setText(info.daywind);ui->forecastDateLab->setText(info.date + " " +info.week);
}bool CWeatherForecast::judgeCityEditText(QString &text)
{// 创建返回值对象bool ret = false;// 获取城市文本QString cityCode = ui->cityEdit->isHidden() ? ui->countyComboBox->currentText() : ui->cityEdit->text();// 判断文本是否为空if(cityCode.isEmpty()){QMessageBox::information(this, "提示", "请输入城市名称或城市编码");}else{// 判断城市名称中是否包含文本if(m_codeInfoMap.keys().contains(cityCode)){// 获取对应城市名的城市编码并赋值text = m_codeInfoMap[cityCode];ret = true;}// 获取城市编码是否包含文本框文本else if(m_codeInfoMap.values().contains(cityCode)){// 城市编码正确并赋值text = cityCode;ret = true;}else{QMessageBox::information(this, "提示", "请输入正确的城市名称或城市编码");}}return ret;
}void CWeatherForecast::on_recvNetworkReply(QNetworkReply *reply)
{// 判断错误码是否为QNetworkReply::NoError(若判断条件成立,则reply对象数据错误)if(reply->error() != QNetworkReply::NoError){// 弹出警告QMessageBox::warning(nullptr, "警告", "数据返回错误");}else{// 创建array容器接收数据QByteArray data;// 读取所有json串data = reply->readAll();// 解析json串parseJson(data);}// 释放内存,防止内存泄漏delete reply;reply = nullptr;
}void CWeatherForecast::sendWeatherInfoRequest()
{if(judgeCityEditText(m_stApiInfo.cityCode)){// 调用组合API链接函数申请APIQUrl url(m_stApiInfo.joinApiLink());// 获取天气预报回执;此处返回的QNetworkReply对象将在on_recvNetworkReply槽函数中体现,可不在此接收释放。       m_networkAccMgr->get(QNetworkRequest(url));}
}void CWeatherForecast::responseMenuAction(QAction *action)
{// 当参数值为空时返回if(nullptr == action){return;}QString text = action->text();if("刷新" == text){// 发送天气信息请求sendWeatherInfoRequest();}else{// 获取城市编辑栏当前显示状态bool isHidden = ui->cityEdit->isHidden();// 将城市编辑栏显示状态取反设置ui->cityEdit->setHidden(!isHidden);// 设置城市选择模式显隐状态ui->selectModeWgt->setHidden(isHidden);if(isHidden){// 获取选择模式下当前城市名QString text = ui->countyComboBox->currentText();// 将当前城市名设置到编辑栏中ui->cityEdit->setText(text);}else{// 获取当前区县编码值QString countyCode = m_stApiInfo.cityCode;// 通过当前区县编码值获取当前区县名QString countyName = m_codeInfoMap.key(m_stApiInfo.cityCode);bool contain = m_codeInfoMap.values().contains(countyCode);// 通过当前区县编码值获取当前城市编码值QString cityCode = countyCode.replace(countyCode.size() - 2, 2, "00");contain = m_codeInfoMap.values().contains(cityCode);// 通过省份编码值获取省份名QString cityName = m_codeInfoMap.key(cityCode);// 包含市辖区时移除市辖区文本cityName = cityName.replace("市辖区", "");// 通过当前区县编码值获取当前省份编码值QString provinceCode = countyCode.replace(countyCode.size() - 4, 4, "0000");contain = m_codeInfoMap.values().contains(provinceCode);// 通过省份编码值获取省份名QString provinceName = m_codeInfoMap.key(provinceCode);// 然后将获取到的三个值更新到下拉框中// 设置省份ui->provinceComboBox->setCurrentText(provinceName);// 设置城市ui->cityComboBox->setCurrentText(cityName);// 设置区县ui->countyComboBox->setCurrentText(countyName);}}
}void CWeatherForecast::on_switchModeBtn_clicked(bool checked)
{// 判断选中状态,获取将要设置对应的文本QString text = checked ? "预报天气" : "实时天气";// 设置当前显示的文本ui->switchModeBtn->setText(text);// 设置API扩展参数变量的新值m_stApiInfo.extensions = checked ? "all" : "base";// 设置当前栈模式对应的窗口ui->stackedWidget->setCurrentIndex(checked ? 1 : 0);// 发送天气信息请求sendWeatherInfoRequest();
}void CWeatherForecast::on_forecastBtnClicked(int index)
{if(index < m_stWeatherInfo.stForecastInfoList.size()){loadForeCastInfo(m_stWeatherInfo.stForecastInfoList.at(index));}
}void CWeatherForecast::on_provinceComboBox_currentIndexChanged(const QString &arg1)
{//! 当省份变化时,城市下拉列表框将要清空ui->cityComboBox->clear();//! 然后添加对应的城市列表ui->cityComboBox->addItems(m_cityInfoMap[arg1].keys());
}void CWeatherForecast::on_cityComboBox_currentIndexChanged(const QString &arg1)
{// 当当前文本内容为空时返回if(arg1.isEmpty()){return;}//! 当城市变化时,区/县下拉列表框将要清空ui->countyComboBox->clear();//! 然后添加对应的区县列表ui->countyComboBox->addItems(m_cityInfoMap[ui->provinceComboBox->currentText()][arg1]);
}void CWeatherForecast::on_countyComboBox_currentIndexChanged(const QString &arg1)
{// 当当前文本内容为空时返回if(arg1.isEmpty()){return;}// 区县改变时将要m_stApiInfo.cityCode = m_codeInfoMap[arg1];// 发送天气信息请求sendWeatherInfoRequest();
}void CWeatherForecast::on_customContextMenuRequested()
{// 创建菜单变量QMenu menu;// 添加菜单选项menu.addAction(new QAction("刷新", &menu));menu.addAction(new QAction("切换城市选择模式", &menu));// 显示菜单,并且显示位置为鼠标位置,并接收返回的对象QAction *action = menu.exec(QCursor::pos());responseMenuAction(action);
}void CWeatherForecast::on_cityEdit_textChanged(const QString &arg1)
{// 当城市编码容器包含当前文本Key值则进入if(m_codeInfoMap.contains(arg1) || m_codeInfoMap.values().contains(arg1)){// 发送天气信息请求sendWeatherInfoRequest();}
}

CWeatherForecast.ui


CWeatherForecast00649450CWeatherForecast实时天气true请输入城市/城市编码0000Qt::Horizontal4020报告时间:报告时间0000000城市标签°CQt::Horizontal4020天气图标天气0风速:风速0风向:风向0湿度:湿度0000080天气图标°C0风速:风速°C0风向:风向星期 日期城市标签天气0064923

总结

本文通过高德开放平台的天气预报模块获取天气预报信息,然后通过Qt各个类实现天气预报的基本功能。因为各个平台的差异,如返回的天数、天气Json格式、信息等各不相同,所以文本也就固定了仅支持高德开放平台提供的天气预报接口。总的来说,功能不算复杂,比较适合新手练习使用.
然后是汇报一下近况,为什么更新变慢了,其一是近期公司项目进入收尾,加班增多;其次则是生活和天气原因,再加上每日私人编程时间有限等情况的影响。但我还是会持续更新,下一章为"天气预报-界面优化篇",敬请期待叭。

相关文章

天气预报系列

Qt之天气预报实现(一)预备篇

相关类的基本使用

Qt读取Json文件(含源码+注释)
Qt读写XML文件(含源码+注释)
Qt之QChart各个图表的简单使用(含源码+注释)
Qt创建右键菜单的两种通用方法(QTableView实现右键菜单,含源码+注释)
Qt之QCompleter的简单使用(自动补全、文本框提示、下拉框提示含源码+注释)

友情提示——哪里看不懂可私哦,让我们一起互相进步吧
(创作不易,请留下一个免费的赞叭 谢谢 ^o^/)

注:文章为作者编程过程中所遇到的问题和总结,内容仅供参考,若有错误欢迎指出。
注:如有侵权,请联系作者删除

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...