Qt之天气预报——功能实现篇(含源码+注释)

news2024/11/24 12:27:35

文章目录

  • 一、功能概述
    • 1.基本功能
    • 2.实时天气模式
    • 3.预报天气模式
  • 二、天气预报功能示例图
    • 1.城市选择(下拉框)
    • 2.城市选择(文本框)
    • 3. 预报天气日期切换
    • 4.刷新操作
  • 三、使用类的简述
    • 3.1 涉及的Qt类
    • 3.2 自定义类
      • 3.2.1 自定义结构体
      • 3.2.2 自定义类
  • 四、遇到的问题
    • 1.图表坐标轴不显示
    • 2.时间轴数据索引位置对应
  • 五、源码
    • 5.1 结构体头文件
    • 5.2 预报天气按钮类
    • 5.3 预报天气模块类
    • 5.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:预报天气天气切换按钮类;响应鼠标点击发送索引值,从而切换天气,原型为QLabel,但是主要响应点击信号,所以在此称之为按钮。

四、遇到的问题

1.图表坐标轴不显示

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

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

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

五、源码

5.1 结构体头文件

StWeatherForecast.h

#ifndef STWEATHERFROECAST_H
#define STWEATHERFROECAST_H

#include <QString>
#include <QHash>
#include <QDate>

// 定义api信息结构体
typedef struct stApiInfo
{
    QString link;   // 链接
    QString apiKey; // API Key
    QString cityCode;   // 城市编码
    QString extensions; // 天气查看类型

    // api信息结构体初始化
    stApiInfo()
    {
        link = "http://restapi.amap.com/v3/weather/weatherInfo?";   // 链接
        apiKey = "自己的APIKEY";    // apiKey
        cityCode = "110000";    // 城市码
        extensions = "base";    // 天气查看类型(实时、预报)
    }

    /**
     * @brief joinApiLink 拼接API链接
     * @return 返回拼接好的API链接
     */
    QString joinApiLink()
    {
        QString ret = "";
        // 拼接API_KEY
        ret = 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<int, QString> weekHash;

public:
    QString date;           // 日期
    QString week;           // 星期
    QString dayweather;     // 日间天气
    QString nightweather;   // 夜间天气
    QString daytemp;        // 日间温度
    QString nighttemp;      // 夜间温度
    QString daywind;        // 日间风向
    QString nightwind;      // 夜间风向
    QString daypower;       // 日间风力等级
    QString nightpower;     // 夜间风力等级

    // 创建无参构造
    stForecastsInfo()
    {
        //! 初始化Hash的值,为方便获取周数据
        //! 此次可通过配置文件读取,不过我在这里就偷一下懒了,hhh
        weekHash[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<stForecastsInfo>  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

5.2 预报天气按钮类

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

#ifndef CFORECASTBTN_H
#define CFORECASTBTN_H

#include <QWidget>

namespace Ui {
class CForecastBtn;
}

class CForecastBtn : public QWidget
{
    Q_OBJECT

public:
    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 <QMouseEvent>

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

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>CForecastBtn</class>
 <widget class="QWidget" name="CForecastBtn">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>72</width>
    <height>66</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item alignment="Qt::AlignHCenter">
    <widget class="QLabel" name="weekLab">
     <property name="text">
      <string>星期</string>
     </property>
    </widget>
   </item>
   <item alignment="Qt::AlignHCenter">
    <widget class="QLabel" name="weatherLab">
     <property name="text">
      <string>天气</string>
     </property>
    </widget>
   </item>
   <item alignment="Qt::AlignHCenter">
    <widget class="QLabel" name="tempLab">
     <property name="text">
      <string>0°C~0°C</string>
     </property>
    </widget>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>

5.3 预报天气模块类

CForecastWidget.h

#ifndef CFORECASTWIDGET_H
#define CFORECASTWIDGET_H

#include "StWeatherForecast.h"
#include <QWidget>
#include <QtCharts/QChart>
#include <QtCharts/QLineSeries>
#include <QValueAxis>
#include <QDateTimeAxis>

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<stForecastsInfo> &infoList);

    /**
     * @brief releaseBtn 释放自定义按钮对象
     */
    void releaseBtn();

signals:
    /**
     * @brief forecastBtnClickedSig 转发按钮点击信号
     * @param index 点击按钮索引
     */
    void forecastBtnClickedSig(int index);

public slots:

private:
    Ui::CForecastWidget     *ui;

    QChart                  *m_lineChart;   // 折线图的chart

    QLineSeries             *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 <QtCharts/QChartView>

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<stForecastsInfo> &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对象中的widget
        QWidget *wgt = child->widget();
        // 判断widget是否为空
        if(nullptr != wgt)
        {
            // 释放widget空间
            delete wgt;
        }
        // 释放widget空间
        delete child;
    }
}

CForecastWidget.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>CForecastWidget</class>
 <widget class="QWidget" name="CForecastWidget">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout" stretch="0,1">
   <property name="spacing">
    <number>0</number>
   </property>
   <property name="leftMargin">
    <number>0</number>
   </property>
   <property name="topMargin">
    <number>0</number>
   </property>
   <property name="rightMargin">
    <number>0</number>
   </property>
   <property name="bottomMargin">
    <number>0</number>
   </property>
   <item>
    <layout class="QHBoxLayout" name="forecastBtnLayout"/>
   </item>
   <item>
    <widget class="QChartView" name="weatherChartView"/>
   </item>
  </layout>
 </widget>
 <customwidgets>
  <customwidget>
   <class>QChartView</class>
   <extends>QGraphicsView</extends>
   <header location="global">qchartview.h</header>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>

5.4 天气预报主类

CWeatherForecast.h

#ifndef CWEATHERFORECAST_H
#define CWEATHERFORECAST_H

#include "StWeatherForecast.h"
#include "CForecastWidget.h"

#include <QMainWindow>
#include <QDomElement>
#include <QNetworkAccessManager>    // 导入请求/接受天气查询API的类
#include <QCompleter>

namespace Ui {
class CWeatherForecast;
}

class CWeatherForecast : public QMainWindow
{
    Q_OBJECT

public:
    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<QString, QMap<QString, QStringList>>   m_cityInfoMap;          // 城市信息容器 <省份,<城市,区县>>

    QMap<QString, QString>                      m_codeInfoMap;          // 编码信息容器 <城市,编码>

    QCompleter                                  *m_completer;           // 过滤器对象

};

#endif // CWEATHERFORECAST_H

CWeatherForecast.cpp

#include "CWeatherForecast.h"
#include "ui_CWeatherForecast.h"

#include <QNetworkReply>
#include <QUrl>
#include <QMessageBox>
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QFile>
#include <QDomDocument>
#include <QApplication>
#include <QDir>
#include <QStringListModel>

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;

    // 初始化ui
    if(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/WeatherForecast.xml";
    if(!loadConfig("./Config/WeatherForecast.xml"))
    {
        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;
    }

    // 释放ui
    if(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");
        // 返回值变为true
        ret = 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)
{
    qDebug() << __func__ << 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链接函数申请API
        QUrl url(m_stApiInfo.joinApiLink());
        // 获取天气预报回执
        qDebug() << __func__ << 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

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>CWeatherForecast</class>
 <widget class="QMainWindow" name="CWeatherForecast">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>649</width>
    <height>450</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>CWeatherForecast</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout_7">
      <item>
       <widget class="QPushButton" name="switchModeBtn">
        <property name="text">
         <string>实时天气</string>
        </property>
        <property name="checkable">
         <bool>true</bool>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLineEdit" name="cityEdit">
        <property name="placeholderText">
         <string>请输入城市/城市编码</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QWidget" name="selectModeWgt" native="true">
        <layout class="QHBoxLayout" name="horizontalLayout_6">
         <property name="leftMargin">
          <number>0</number>
         </property>
         <property name="topMargin">
          <number>0</number>
         </property>
         <property name="rightMargin">
          <number>0</number>
         </property>
         <property name="bottomMargin">
          <number>0</number>
         </property>
         <item>
          <widget class="QComboBox" name="provinceComboBox"/>
         </item>
         <item>
          <widget class="QComboBox" name="cityComboBox"/>
         </item>
         <item>
          <widget class="QComboBox" name="countyComboBox"/>
         </item>
        </layout>
       </widget>
      </item>
      <item>
       <spacer name="horizontalSpacer_2">
        <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>40</width>
          <height>20</height>
         </size>
        </property>
       </spacer>
      </item>
      <item>
       <widget class="QLabel" name="label_4">
        <property name="text">
         <string>报告时间:</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLabel" name="reportTimeLab">
        <property name="text">
         <string>报告时间</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
    <item>
     <widget class="QStackedWidget" name="stackedWidget">
      <property name="currentIndex">
       <number>0</number>
      </property>
      <widget class="QWidget" name="page">
       <layout class="QVBoxLayout" name="verticalLayout_2" stretch="3,1">
        <property name="spacing">
         <number>0</number>
        </property>
        <property name="leftMargin">
         <number>0</number>
        </property>
        <property name="topMargin">
         <number>0</number>
        </property>
        <property name="rightMargin">
         <number>0</number>
        </property>
        <property name="bottomMargin">
         <number>0</number>
        </property>
        <item>
         <layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0" columnstretch="1,1,3">
          <property name="spacing">
           <number>0</number>
          </property>
          <item row="0" column="0">
           <widget class="QLabel" name="liveWeatherCityLab">
            <property name="text">
             <string>城市标签</string>
            </property>
           </widget>
          </item>
          <item row="0" column="1">
           <widget class="QLabel" name="liveTempLab">
            <property name="text">
             <string>°C</string>
            </property>
           </widget>
          </item>
          <item row="0" column="2" rowspan="2">
           <spacer name="horizontalSpacer">
            <property name="orientation">
             <enum>Qt::Horizontal</enum>
            </property>
            <property name="sizeHint" stdset="0">
             <size>
              <width>40</width>
              <height>20</height>
             </size>
            </property>
           </spacer>
          </item>
          <item row="1" column="0">
           <widget class="QLabel" name="liveWeatherImgLab">
            <property name="text">
             <string>天气图标</string>
            </property>
           </widget>
          </item>
          <item row="1" column="1">
           <widget class="QLabel" name="liveWeatherLab">
            <property name="text">
             <string>天气</string>
            </property>
           </widget>
          </item>
         </layout>
        </item>
        <item>
         <layout class="QHBoxLayout" name="horizontalLayout_2">
          <item>
           <layout class="QHBoxLayout" name="horizontalLayout">
            <property name="spacing">
             <number>0</number>
            </property>
            <item>
             <widget class="QLabel" name="lab">
              <property name="text">
               <string>风速:</string>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QLabel" name="liveLindPowerLab">
              <property name="text">
               <string>风速</string>
              </property>
             </widget>
            </item>
           </layout>
          </item>
          <item>
           <layout class="QHBoxLayout" name="horizontalLayout_4">
            <property name="spacing">
             <number>0</number>
            </property>
            <item>
             <widget class="QLabel" name="label_11">
              <property name="text">
               <string>风向:</string>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QLabel" name="liveWindDirectionLab">
              <property name="text">
               <string>风向</string>
              </property>
             </widget>
            </item>
           </layout>
          </item>
          <item>
           <layout class="QHBoxLayout" name="horizontalLayout_5">
            <property name="spacing">
             <number>0</number>
            </property>
            <item>
             <widget class="QLabel" name="label_12">
              <property name="text">
               <string>湿度:</string>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QLabel" name="liveHumidityLab">
              <property name="text">
               <string>湿度</string>
              </property>
             </widget>
            </item>
           </layout>
          </item>
         </layout>
        </item>
       </layout>
      </widget>
      <widget class="QWidget" name="page_2">
       <layout class="QVBoxLayout" name="forecastLayout">
        <property name="spacing">
         <number>0</number>
        </property>
        <property name="leftMargin">
         <number>0</number>
        </property>
        <property name="topMargin">
         <number>0</number>
        </property>
        <property name="rightMargin">
         <number>0</number>
        </property>
        <property name="bottomMargin">
         <number>0</number>
        </property>
        <item>
         <layout class="QGridLayout" name="gridLayout">
          <property name="rightMargin">
           <number>80</number>
          </property>
          <item row="1" column="0">
           <widget class="QLabel" name="forecastWeatherImgLab">
            <property name="text">
             <string>天气图标</string>
            </property>
           </widget>
          </item>
          <item row="1" column="1">
           <widget class="QLabel" name="forecastHighTempLab">
            <property name="text">
             <string>°C</string>
            </property>
           </widget>
          </item>
          <item row="1" column="2">
           <layout class="QHBoxLayout" name="horizontalLayout_8">
            <property name="spacing">
             <number>0</number>
            </property>
            <item>
             <widget class="QLabel" name="lab_2">
              <property name="text">
               <string>风速:</string>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QLabel" name="forecastWindPowerLab">
              <property name="text">
               <string>风速</string>
              </property>
             </widget>
            </item>
           </layout>
          </item>
          <item row="2" column="1">
           <widget class="QLabel" name="forecastLowTempLab">
            <property name="text">
             <string>°C</string>
            </property>
           </widget>
          </item>
          <item row="2" column="2">
           <layout class="QHBoxLayout" name="horizontalLayout_9">
            <property name="spacing">
             <number>0</number>
            </property>
            <item>
             <widget class="QLabel" name="label_14">
              <property name="text">
               <string>风向:</string>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QLabel" name="forecastWindDirectionLab">
              <property name="text">
               <string>风向</string>
              </property>
             </widget>
            </item>
           </layout>
          </item>
          <item row="0" column="1">
           <widget class="QLabel" name="forecastDateLab">
            <property name="text">
             <string>星期 日期</string>
            </property>
           </widget>
          </item>
          <item row="0" column="0">
           <widget class="QLabel" name="forecastWeatherCityLab">
            <property name="text">
             <string>城市标签</string>
            </property>
           </widget>
          </item>
          <item row="2" column="0">
           <widget class="QLabel" name="forecastWeatherLab">
            <property name="text">
             <string>天气</string>
            </property>
           </widget>
          </item>
         </layout>
        </item>
       </layout>
      </widget>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menuBar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>649</width>
     <height>23</height>
    </rect>
   </property>
  </widget>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <resources/>
 <connections/>
</ui>

总结

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

相关文章

天气预报系列

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

相关类的基本使用

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/53516.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于PHP的玩偶玩具商城网站设计

目 录 摘 要 I Abstract II 第1章 绪论 1 1.1 定制商城网站背景及意义 1 1.1.1 开发背景 1 1.1.2 开发意义 1 1.2研究现状 2 1.2.1个性化定制现状 2 1.2.2 定制类网站技术现状 3 1.3 研究主要内容 3 第2章 玩偶定制网站需求分析 4 2.1注册 4 2.2.1登陆 4 2.2.2账户中心 5 2.2.3…

计算机网络笔记1 概述

计算机网络笔记1 概述笔记前言&#x1f497;一、计算机网络概述&#x1f60d;二、计算机网络的性能指标&#x1f4a5;1、速率2、带宽3、吞吐量4、时延5、时延带宽积6、往返时间(Round-Trip-Time)7、利用率8、丢包率三、计算机网络的体系结构&#x1f525;四、计算机网络中的专业…

基于神经气体网络的图像分割与量化(Matlab代码实现)

&#x1f352;&#x1f352;&#x1f352;欢迎关注&#x1f308;&#x1f308;&#x1f308; &#x1f4dd;个人主页&#xff1a;我爱Matlab &#x1f44d;点赞➕评论➕收藏 养成习惯&#xff08;一键三连&#xff09;&#x1f33b;&#x1f33b;&#x1f33b; &#x1f34c;希…

菜狗杯Misc抽象画wp

目录一、拿到题目先干嘛二、具体的解密操作1.把文本放到CyberChef中用Magic解密2.把完整的解密内容复制出来3.打开010并以hex格式粘贴内容4.点一下HEX5.保存成png三、用工具拿到隐写内容一、拿到题目先干嘛 题目附件是一个txt&#xff0c;打开里面就是各种字符&#xff0c;拿去…

操作系统考试速成01

1. ___分时____操作系统允许在一台主机上同时连接多台终端&#xff0c;多个用户可以通过各自的终端同时交互地使用计算机 2.分时操作系统通常采用____时间片轮转___策略为用户服务 3.批处理操作系统&#xff1a;多个作业给到计算机系统 3.实时操作系统&#xff1a;计算机系统…

使用WPS Office模糊处理图片-可用作浏览器背景

前文转到&#xff1a;给浏览器设置一个图片背景/主题 使用WPS Office模糊处理图片-可用作浏览器背景&#xff0c;步骤如下&#xff1a; 1、打开WPS Office&#xff0c;新建一个空白PPT&#xff0c;或者右键-新建-PPT演示文稿 2、将你的图片插入到空白页上&#xff0c;点击 插入…

[激光原理与应用-27]:《激光原理与技术》-13- 激光产生技术 - 激光稳频技术

目录 前言&#xff1a; 第1章 什么频率的稳定性和可复现性。 1.1 频率的稳定度 1.2 频率复现性 第2章 影响激光频率稳定的因素。 2.1 温度引起腔长变化。则有 2.2 大气变化引起折射率的变化。 2.3 机械振动对频率稳定性的影响。 2.4 外部因素 2.5 内部因素 第3章 常…

Day16--购物车页面-商品列表-基于props封装radio的勾选状态

提纲挈领&#xff1a; 我的操作&#xff1a; 1》打开 my-goods.vue 组件的源代码&#xff0c;为商品的左侧图片区域添加 radio 组件&#xff1a; 2》给类名为 goods-item-left 的 view 组件添加样式&#xff0c;实现 radio 组件和 image 组件的左右布局&#xff1a; 1》和2》的…

9 个你可能从未使用过的很棒的 CSS 属性

如今&#xff0c;网络上的每个网站或 Web 应用程序都需要大量的 CSS 代码来装饰它们&#xff0c;这样可以使网站看起来既漂亮又出众。我个人认为如果不使用 CSS&#xff0c;我们将永远不会有一个可以帮助我们脱颖而出的优秀网页设计。 CSS 是一种非常有用的样式表语言&#xf…

记liunx服务器java程序无法访问的问题处理

查看java程序的运行日志&#xff1a; 通过日志查看&#xff0c;在凌晨3点半之后就没有再打印信息了&#xff0c;说明大概在这个点程序已经挂了。 查看liunx的系统日志&#xff1a; 查看linux系统级别的message信息&#xff1a; 在3点半出现了OOM&#xff08;内存溢出&#xff0…

在构建 Web3 前,需先知道什么是区块链,毕竟 Web3 是基于区块链

什么是区块链 要说什么是区块链&#xff0c;那么这里就不得不提比特币了&#xff0c;它是一种点对点&#xff08;Peer to Peer&#xff0c;P2P&#xff09;形式的去中心化加密货币&#xff0c;点对点的传输意味着是一个去中心化的支付系统。比特币的概念最初是由中本聪在2008年…

传奇开服架设教程

传奇架设其实很简单 很多网友非常爱玩这款游戏&#xff0c;可能还有朋友不知道怎么架设这款游戏 今天特意写篇传奇架设教程&#xff0c;希望大家都能打造出真正属于自己的传奇 首先传奇架设需要准备以下几个软件 准备工具&#xff1a; 1、传奇服务端&#xff08;版本&#…

excel求和怎么操作?这三个简单操作方法,轻松掌握

​excel作为常用的办公软件&#xff0c;不少人都会在工作的时候使用到。其中最常用的一个功能就是使用excel进行求和了。excel求和怎么操作&#xff1f;excel求和的快捷键是什么&#xff1f;今天小编给大家带来了excel求和的三种方法&#xff0c;亲测好用&#xff0c;有需要的小…

C++【类的自动类型转换和强制类型转换】,总要了解一下

学习目标 学习类的自动类型转换和强制类型转换 学习内容 &#x1f496;类的强制转换数据类型和自动转换数据&#xff1a; 类的强制转换数据类型 想让类的对象强制转换为基本数据类型&#xff0c;需要在类中添加类的转换函数 1、转换函数必须是类方法2、转换函数不能指定返回…

1.2 无监督学习和强化学习

1.2 无监督学习和强化学习无监督学习定义无监督学习与监督学习的区别相关概念流程图强化学习无监督学习 定义 无监督学习 (Unsupervised Learning&#xff09;是指从无标注数据中学习预测模型的机器学习问题&#xff0c;其本质是学习数据中的统计规律或潜在结构。 无监督学习…

算法与数据结构介绍

算法与数据结构介绍 算法和数据结构不受语言限制&#xff0c;每种编程语言都有关于自己的实现 算法 什么是算法 算法是指解题方案的准确而完整的描述&#xff0c;算法是一系列解决问题的清晰指令&#xff0c;算法使用系统的方法来解决问题的机制。 算法作用 对于实际业务…

Kamiya丨Kamiya艾美捷大鼠成纤维细胞生长因子2说明书

Kamiya艾美捷大鼠成纤维细胞生长因子2&#xff0c;碱性&#xff08;FGF2&#xff09;ELISA预期用途&#xff1a; 该试剂盒是一种夹心酶免疫分析法&#xff0c;用于在体外定量测量大鼠FGF2血清、血浆、组织匀浆、细胞裂解物、细胞培养上清液和其他生物液体。对于仅供研究使用。…

laravel vue tailwind normalize

下载laravel最新7.x composer create-project --prefer-dist laravel/laravle blog 7.x-dev cd blog valet link blog valet links blog.test 测试通过后&#xff0c;开始安装tailwind npm i npm i tailwindcss autoprefixer postcss7 都是最新版应该也没有什么问题 在根目录下…

CSS自定义属性与前端页面的主题切换

基于级联变量的CSS自定义属性&#xff0c;已经出来很多年了。 虽然有less、sass等预处理器大行其道&#xff0c;但是自定义属性也有它的特点和用处&#xff0c;诸如在js中读写、作用域设置等等&#xff0c;在处理UI主题切换等功能上也发挥着很大的作用。 自定义属性 CSS自定义…

电影《乌云背后的幸福线》观后感

首先感谢同学推荐这部电影&#xff0c;以前没看过这部电影《乌云背后的幸福线》&#xff0c;看完之后一点想法&#xff0c;希望你能给点启发吧&#xff0c;整部电影讲述一对分别离异的男女再次相爱的故事。打个比方&#xff0c;就好像因为感情不和的小王夫妇离婚了&#xff0c;…