基于QT5的在线天气预报

目录

1.整体效果、技术点

1.1 样式表的设置

1.2 JSON数据格式

1.3 HTTP请求

1.4 事件

1.5 绘图

1.6 资源文件

2.JSON数据格式

2.1 什么是JSON?

2.2JSON的两种数据格式

2.2.1 JSON数组

2.2.2 JSON对象

2.3JSON 在线解析

2.4 Qt中使用JSON

2.4.1 JSON相关的类

2.4.1.1 QJsonObject

2.4.1.2 QJsonArray

2.4.1.3 QJsonValue

2.4.1.4 QJsonDocument

2.4.2构建JSON字符串

2.4.3 解析JSON字符串

3. HTTP通信

3.1 HTTP概述

3.1.1 两种架构

3.1.1.1 B/S 架构

3.1.1.2 C/S 架构

3.1.2 基于请求响应的模式

3.1.3 无状态

3.14 请求报文

3.15 响应报文

3.16 请求方式

3.2 Postman

3.3 Qt实现HTTP请求

3.3.1 创建“网络访问管理”对象

3.3.2 关联信号槽

3.3.3 发送请求

3.3.4 接收数据

4.详细实现步骤

4.1新建工程,右键退出

4.1.1 创建工程

4.1.2 设置窗口大小,无边框

4.1.3 实现右键退出

4.2界面布局

4.2.1 设置窗口背景

4.2.2 标签显示图片

4.2.3 修改控件名称

4.3 窗口移动,系统托盘

4.3.1 重写父类的鼠标移动事件

4.3.2设置系统托盘

4.4 HTTP请求天气数据

4.4.1 添加网络模块

4.4.2 声明网络对象,槽函数

4.4.3 关联信号槽

4.4.4 发送http请求

4.4.5 接收服务端数据

4.5 解析天气数据

4.5.1 定义两个类

4.5.1.1 Today

4.5.1.2 Day

4.5.2 解析数据

4.6 更新UI界面

4.6.1 创建控件数组

4.6.2 更新UI

4.6.2.1天气图标处理

4.7城市编码

4.7.1 创建一个工具类

4.7.2 调用这个工具类

4.7.3 实现程序外设置城市

4.8 绘制温度曲线

4.8.1 安装事件过滤器

4.8.2 重写eventFilter方法

4.8.3 绘制曲线

4.9 完整代码

4.9.1 mainwindow.h

4.9.2 mainwindow.cpp

4.9.3 weatherdata.h

4.9.4 weathertool.h

5.项目打包

5.1.将编译模式设置为release

5.2. 拷贝一条文件路径

5.3.运行windeployqt.exe,获取qt下的dll文件

5.4 将系统中,qt相关的环境变量删除

5.5使用Enigma Virtual Box进行打包操作


1.整体效果、技术点

在左上角输入要查询的城市,然后点击查询按钮,就会发生http请求给服务器,请求回来的天气数据为JSON格式通过解析JSON可以获取以下信息:

  • 今天的信息

    温度,湿度,风向,风力,天气类型(晴,多云,小雨等),PM2.5,温馨提示,感冒指数,日出日落

  • 未来15天的信息

    日期,星期,天气类型(晴,多云,小雨等),PM2.5,最高温,最低温

1.1 样式表的设置

合理地使用样式表,可以使界面更加美观,这里设置的样式表如下:

  • 背景图片

为整个窗体设置一张背景图片

  • 背景色

设置控件背景透明,或者设置一个透明度

  • 圆角

为控件设置圆角

  • 字体颜色和大小

为控件设置合适的字体颜色和字体大小

1.2 JSON数据格式

HTTP服务端返回的天气数据,是JSON格式

使用Qt提供的,解析JSON相关的类,可以很方便地解析出其中的字段

1.3 HTTP请求

根据HTTP服务端提供的接口,发送http请求,获取天气数据

1.4 事件

为了界面的美观,我们将窗口设置为无标题栏,这样就无法通过右上角的【关闭】按钮,退出程序

因此增加了右键菜单退出的功能

还重写了鼠标移动事件,让窗口可以跟随鼠标移动

1.5 绘图

绘制高低温曲线,根据每天高低温数据,可以绘制一个曲线,更直观地展示温度变化趋势

1.6 资源文件

根据不同的天气类型,还可以用不同的图标进行展示,更加直观

而这些图标通常会放到资源文件中,这样它们可以一同被打包进qt的可执行程序中

2.JSON数据格式

2.1 什么是JSON?

在讲解 JSON之前,首先想这么一种场景:

通常,客户端和服务端要进行通信,那么通信采用什么数据格式呢?

比如C++写的服务端,创建一个Person对象:

Class Person{
	string name;
	string gender;
	int age;
};

怎么将服务端创建的Person对象,传递到客户端呢?

直接传Person对象肯定是不合适的,因为客户端可能甚至不是C++写的,可能是java写的,java不认识C++中的对象,更有甚者,客户端是一个单片机的设备,是用C语言写的,C语言是面向过程的,压根就没有类和对象的概念

此时就需要一种通用的数据格式,就是JSON

JSON(JavaScript Object Notation),中文名JS对象表示法,因为它和JS中的对象的写法很类似

通常说的JSON,其实就是JSON字符串,本质上是一种特俗格式的字符串

JSON是一种轻量级的数据交换格式,客户端和服务端数据交互,基本都是JSON格式的

特点

  1. 便于阅读和书写

    除了JSON格式,还有一种数据传输格式XML,相对于XMLJSON更加便于阅读和书写

  2. 独立于编程语言

JSON完全独立于编程语言,并不是说名字里有JavaScript,就只能在JavaScript中使用,不是这样的

JSONJavaScript的关系,就类似于雷锋和雷锋塔,并没有什么关系

几乎在所有的编程语言和开发环境中,都有解析和生成JSON字符串的库,比如:

#C
Jansson,cJSON

#C++
jsonCpp,JSON or Modern C++

#Java
json-lib,org-json

#Android
Gson,FastJson

#Qt
QJsonxxx
  1. 网络传输的标准数据格式

基于以上特点,JSON成为网络传输使用率最高的数据格式

2.2JSON的两种数据格式

JSON 有两种数据格式

  • JSON 对象

  • JSON 数组

规则:被大括号包裹的是JSON 对象;被中括号包裹的JSON数组

2.2.1 JSON数组

JSON数组格式:

[元素1,元素2,元素3,...元素n]

类似于C/C++中的数组,元素之间是以逗号分隔

不同的是,JSON数组的元素可以是不同的数据类型,包括:整形,浮点型,字符串,布尔类型,JSON数组,JSON对象,空值

JSON数组中的元素是同一类型

//元素中的类型都是数字
[1,2,3,4]

//元素类型都是字符串
["SPring","Summer","Autumn","Winter"]

JSON 数组中的元素是不同类型

[1,2.5,"hello",true,false,null]

JSON数组的嵌套

[
	[1,2,3,4],
	["SPring","Summer","Autumn","Winter"],
	[1,2.5,"hello",true,false,null]
]

JSON数组嵌套 JSON对象

[
	{
		"name":"TOM",
		"age":18,
		"gender":"male"
	},
	{
		"name":"TOM",
		"age":18,
		"gender":"male"
	}
]

2.2.2 JSON对象

JSON对象格式:

{
	"key1":value1,
	"key2":value2,
	"key3":value3,
}

JSON 对象内部使用键值对的方式来组织

键和值之间使用冒号分隔,多个键值之间使用逗号分隔

键是字符串类型,值的类型可以是:整形,浮点型,字符串,布尔类型,JSON数组,JSON对象,空值

eg:

{
	"name":"TOM",
	"age":18,
	"gender":"male"
}

JSON 对象中,还可以嵌套JSON对象和JSON数组

{
	"name":"China",
	"info":{
		"capitcal":"beijing",
		"asian":"true",
		"founded":1949
	},
    "provinces":[{
        "name":"guangdong",
        "capital":"shenzheng"
    },{
        "name":"fujian",
        "capital":"xiamen"
    }]
}

2.3JSON 在线解析

JSON本质是一种特殊格式的字符串

实际工作中,这个JSON字符串可能是自己手写的,也可能来自网络接收的数据

而我们获取到的JSON字符串,可能是自己写的,也可能是服务端返回的,

是压缩的格式,没有换行和缩进,不方便判断格式是否正确

这时候就要用JSON在线解析工具,来校验JSON的格式

2.4 Qt中使用JSON

QT5.0开始提供了对 JSON的支持,使用Qt提供的 JSON类,可以很方便地生成 JSON字符串,以及解析 JSON字符串

2.4.1 JSON相关的类

Qt提供的与 JSON相关的类,有四个

  • QJsonObject

  • QJsonArray

  • QJsonValue

  • QJsonDocument

2.4.1.1 QJsonObject

QJsonObject封装了JSON中的对象,可以存储多个键值对

其中,键为字符串类型,值为QJsonValue类型

  • 创建一个 QJsonObject对象

QJsonObject::QJsonObject();
  • 将键值对添加到 QJsonObject对象中

QJsonObject::iterator insert(const QString &key,const QJsonValue &vlaue)
  • 获取 QJsonObject对象中键值对的个数

int QJsonObject::count() const;
int QJsonObject::size() const;
int QJsonObject::length() const;
  • 通过key得到value

QJsonValue QJsonObject::value(const QString &key) const;
QJsonValue QJsonObject::operator[](const QString &key)const;
  • 检查 key是否存在

iterator QJsonObject::find(const QString &kev);
bool QJsonObject::contains(const QString &kev);

2.4.1.2 QJsonArray

QJsonArray 封装了JSON中的数组,数组中元素的类型统一为 QJsonValue类型

  • 创建一个 QJsonArray

QJsonArray::QJsonArray();
  • 添加数组元素

//添加头部和尾部
void QJsonArray::append(const QJsonArray &value);
void QJsonArray::prepend(const QJsonArray &value);

//插入到 i 的位置之前
void QJsonArray::insert(int i , const QJsonArray &value);

//添加到头部和尾部
void QJsonArray::push_back(const QJsonArray &value);
void QJsonArray::push_front(const QJsonArray &value);
  • 获取 QJsonArray中元素个数

int QJsonArray::count() const;
int QJsonArray::size() const;
  • 获取元素的值

//获取头部和尾部
QJsonValue QJsonArray::first()const;
QJsonValue QJsonArray::last()const;

//获取指定位置
QJsonValue QJsonArray::at(int i)const;
QJsonValueRef QJsonArray::operator[](int i);
  • 删除元素

//删除头部和尾部
void QJsonArray::pop_back();
void QJsonArray::pop_frony();

void QJsonArray::removeFirst();
void QJsonArray::removeLast();

//删除指定位置
void QJsonArray::removeAt(int i);
QJsonValue QJsonArray::takeAt(int i);

2.4.1.3 QJsonValue

它封装了 JSON支持的六种数据类型:

//布尔类型
QJsonValue::Bool
    
//浮点类型(包括整形)
QJsonValue::Double
    
//字符串类型
QJsonValue::String
    
//Json数组类型
QJsonValue::Array
    
//Json对象类型
QJsonValue::Object
    
//空值类型
QJsonValue::Null

可以通过以下方式构造 QJsonValue对象

//字符串
QJsonValue(const char *s);
QJsonValue(QLatinlString s);
QJsonValue(const QString &s);

//整形 和 浮点型
QJsonValue(qint64 v);
QJsonValue(int v);
QJsonValue(double v);

//布尔类型
QJsonValue(bool b);

//JSON对象
QJsonValue(const QJsonObject &o);

//JSON数组
QJsonValue(const QJsonArray &a);

//空值类型
QJsonValue(QJsonValue::Type type =Null);

如果已经得到了一个 QJsonValue对象,如何判断其内部封装的是什么类型的数据:

使用以下判断函数

//是否是字符串
bool isString()const;

//是否是整形 和 浮点型
bool isDouble()const;

//是否是布尔类型
bool isBool()const;

//是否是JSON对象
bool isObject()const;

//是否是JSON数组
bool isArray()const;

//是否是未定义类型(无法识别的类型)
bool isUndefined()const;

//是否是空值类型
bool isNull()const;

通过以上判断函数,获取到其内部数据的实际类型之后,如果有需求就可以再次将其转换为对应的基础数据类型,对应的 API函数如下:

/转换为字符串类型
QString toString()const;
QString toString(const QString &defaultValue)const;

//转换为整形 
int toInt(int defaultValue = 0)const;

//转换为浮点型
double toDouble(double defaultValue = 0)const;

//转换为布尔类型
bool toBool(bool defaultValue =false)const;

//转换为JSON对象
QJsonObject toObject(const QJsonObject &defaultValue)const;
QJsonObject toObject()const;

//转换为JSON数组
QJsonArray toArray(const QJsonArray &defaultValue)const;
QJsonArray toArray()const;

2.4.1.4 QJsonDocument

它封装了一个完整的 JSON文档

它可以从 UTF-8编码的基于文本的表示,以及Qt本身的二进制格式读取和写入该文档

QJsonObjectQJsonArray这两个对象是不能直接转换为字符串类型的,需要通过 QJsonDocument类来完成二者的转换

  • QJsonObject / QJsonArray -->字符串

//1.创建QJsonDocument对象
//以 QJsonObject或者QJsonArray 为参数来创建QJsonDocument对象
QJsonDocument::QJsonDocument(const QJsonObject &object);
QJsonDocument::QJsonDocument(const QJsonArray &array);

//2.将 QJsonDocument对象中的数据进行序列化
//通过调用toXXX()方法就可以得到文本格式或者二进制的Json 字符串了
QByteArray QJsonDocument::toBinaryData()  const;		//二进制格式的json字符串
QByteArray QJsonDocument::toJson(JsonFormat format = Indented)const;		//文本格式

//3.使用得到的字符串进行数据传输,或者保存到文件
  • 字符串 -->QJsonObject / QJsonArray

通常,通过网络接收或者读取磁盘文件,会得到一个 JSON格式的字符串,之后可以按照如下步骤,解析出 JSON字符串中的一个个字段

//1.将 JSON字符串转换为QJsonDocument对象
[static]QJsonDocument QJsonDocument::fromBinaryData(const QByteArray &data, DataValidation validation = Validate);
[static]QJsonDocument QJsonDocument::fromJson(const QByteArray &json, QJsonParseError *error= Q_NULLPTR);

//2.将文档对象转换为json 数组/对象
//2.1判断文档对象中存储的数据,是JSON数组还是JSON对象
bool QJsonDocument::isArray()const;
bool QJsonDocument::isObject()const;

//2.2之后,就可以转换为JSON 数组或 JSON对象
QJsonObject QJsonDocument::object()const;
QJsonArray QJsonDocument::array()const;

//3.调用QJsonArray /QJsonObject 类提供的 API获取存储在其中的数据

2.4.2构建JSON字符串

在网络传输时,通常是传输的 JSON字符串

传输之前,首先要生成 JSON字符串,

{
	"name":"China",
	"info":{
		"capitcal":"beijing",
		"asian":"true",
		"founded":1949
	},
    "provinces":[{
        "name":"guangdong",
        "capital":"guangzhou"
    },{
        "name":"fujian",
        "capital":"fuzhou"
    }]
}

Qt中实现代码(控制程序即可,不用界面)

#include <QCoreApplication>
#include<QJsonObject>
#include<QJsonArray>
#include<QJsonDocument>

#include<QFile>
#include<QDebug>

void writeJson()
{
    QJsonObject rootObj;

    //1.插入name字段
    rootObj.insert("name","China");

    //2.插入info字段
    QJsonObject infoObj;
    infoObj.insert("capitcal","beijing");
    infoObj.insert("asian","true");
    infoObj.insert("founded",1949);
    rootObj.insert("info",infoObj);

    //3.插入prvince字段
    QJsonArray provinceArray;

    QJsonObject provinceShandongObj;
    provinceShandongObj.insert("name","guangdong");
    provinceShandongObj.insert("capital","guangzhou");

    QJsonObject provinceFujianObj;
    provinceFujianObj.insert("name","fujian");
    provinceFujianObj.insert("capital","fuzhou");

    provinceArray.append(provinceShandongObj);
    provinceArray.append(provinceFujianObj);

    rootObj.insert("provinces",provinceArray);

    //4.将rootObj转换为json字符串
    QJsonDocument doc(rootObj);     //把 JSON字段 作为参数放入QJsonDocument中
    QByteArray json =doc.toJson();

    //5.1 打印输出
    qDebug()<<QString(json).toUtf8().data();

    //5.2将json字符串写入到文件
    QFile file("E:\\VS_project\\qt\\qt_B\\JSON_demo\\china.json");
    file.open(QFile::WriteOnly);
    file.write(json);
    file.close();
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    writeJson();

    return a.exec();
}

2.4.3 解析JSON字符串

通常接收网络数据的格式是 JSON格式

在接受完毕之后,需要解析出其中的每一个字段,根据各个字段的值做相应的显示或者其他处理

接下来使用Qt提供的工具类,读取文件中的 JSON字符串,把其中的字段解析处理

void fromJson()
{
    //1.读取文件
    QFile file("E:\\VS_project\\qt\\qt_B\\JSON_demo\\china.json");
    file.open(QFile::ReadOnly);
    QByteArray json =file.readAll();    //获取china.jason中的数据到QByteArray类json中
    file.close();

    //2.解析json
    QJsonDocument doc =QJsonDocument::fromJson(json);   //将获取到的json 作为参数放入QJsonDocument类的doc中
    if(!doc.isObject()){                                //判断doc是不是一个json对象
        qDebug()<<"Not an object";
        return;
    }

    QJsonObject obj=doc.object();       //将doc的json对象,存入obj
    QStringList keys=obj.keys();        //获取obj中的key
    for(int i=0;i<keys.size();i++)      //遍历key
    {
        //获取key-value
        QString key=keys[i];
        QJsonValue value=obj.value(key);

        //对json对象中的元素进行判断
        if(value.isBool()){
            //如果是bool类型
            qDebug()<<key<<":"<<value.toBool();
        }
        else if(value.isString()){
            qDebug()<<key<<":"<<value.toString();
        }
        else if(value.isDouble()){
            qDebug()<<key<<":"<<value.toInt();
        }
        else if(value.isObject()){
            qDebug()<<key<<":";

            QJsonObject infoObj =value.toObject();      //这里应该也要进行判断,和上面一样,节省时间直接写了
            QString capital =infoObj["capital"].toString();
            bool asian =infoObj["asian"].toBool();
            int founded =infoObj["founded"].toInt();

            qDebug()<< "  "<<"capital"<<":"<<capital;
            qDebug()<< "  "<<"asian"<<":"<<asian;
            qDebug()<< "  "<<"founded"<<":"<<founded;
        }
        else if(value.isArray()){
            qDebug()<<key<<":";
            QJsonArray provinceArray = value.toArray();

            for(int i=0;i<provinceArray.size();i++){
                QJsonObject provinceObj=provinceArray[i].toObject();

                QString name=provinceObj["name"].toString();
                QString capital=provinceObj["capital"].toString();

                qDebug()<<"  "<<"name"<<":"<<name;
                qDebug()<<"  "<<"capital"<<":"<<capital;
            }
        }
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    //writeJson();

    fromJson();
    return a.exec();
}

3. HTTP通信

3.1 HTTP概述

HTTP :超文本传输协议(HyperTextTransferProtocol)

HTTP 是浏览器端 web通信的基础

3.1.1 两种架构
3.1.1.1 B/S 架构

B/S 架构:Browser/Server ,浏览器/服务器 架构

B:浏览器,比如IE Google

S: 服务器,比如Apache , nginx等

3.1.1.2 C/S 架构

C/S 架构: Client/Server 客户端/服务器架构

B/S 架构相对于 C/S 架构,客户机上无需安装任何软件,使用浏览器即可访问服务器

因此,越来越多的C/S架构正被B/S架构所替代

3.1.2 基于请求响应的模式

 

HTTP 协议永远都是客户端发起请求,服务端做出响应

也就是说,请求必定是从客户端发起的,服务器端在没有接收到请求之前不会发送任何响应

这就无法实现这样一种场景:服务端主动推送消息给客户端

3.1.3 无状态

当浏览器第一次发送请求给服务器时,服务器做出了响应;

当浏览器第二次发送请求给服务器时,服务器同样可以做出响应,但服务器并不知道第二次的请求和第一次来自同一个浏览器

也就是是说,服务器是不会记住你是谁的,所以是无状态的

而如果要使 HTTP协议有状态,就可以使浏览器访问服务器时,加入cookie

这样,只要你在请求时有了这个cookie,服务器就能通过cookie知道,你就是之前那个浏览器

这样的话,HTTP协议就有状态了

3.1.4 请求报文

请求报文由四部分组成:

请求行 + 请求头(请求首部字段) + 空行 + 实体
  • 请求行

请求行里面有:

请求方法 :比如 GETPOST

资源对象 :URL

协议名称和版本号 : HTTP/1.1

POST /custom/a287498349493783/web/cstm?stm=10351516512615 HTTP/1.1

POST														//即请求方法
/custom/a287498349493783/web/cstm?stm=10351516512615		//即URL
HTTP/1.1													//即协议和版本
  • 请求头

请求头由于告诉服务器该请求的一些消息,起到传递额外信息的,目的

Host: api.growingio.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Length:264
Origin:https://leetcode-cn.com
Sec-Fetch-Dest:empty
Sec-Fetch-Mode:no-cors
Sec-Fetch-Site:cross-site
Referer:https://leetcode-cn.com/
  • 空行

空行是为了区分 请求头 和 请求实体

  • 请求实体

请求实体即真正所需要传输的数据

3.1.5 响应报文

响应报文同样是由四部分组成:

状态行 + 响应头 + 空行 + 消息体
  • 状态行

状态行主要由三部分组成:

HTTP 版本

状态码 (表示相应的结果)

原因短语(解释)

HTTP/2 200 OK

HTTP/2		//协议和版本
200			//状态码 200代表OK,表示请求成功;404代表NOT FOUND 表示请求失败,所请求资源未在服务器上发现
OK			//原因短语
  • 响应头(响应报文首部)

和请求报文首部一样,响应报文首部同样是为了传递额外信息,例如:

date: Tue, 05 Apr 2022 10:48:17 GMT 			//响应时间
content-type:application/json					//响应格式
content-length:127								//长度
strict-transport-security: max-age=31536000
X-Firefox-Spdy:h2
  • 空行

同样是为了区别响应实体和响应首部

  • 响应实体

真正存储响应信息的部分

3.1.6 请求方式

HTTP 常用的请求方法有很多,但最常用的是 GETPOST

二者最主要的区别是:

  • GET 请求的参数位于 URL中,会显示在地址栏上

  • POST 请求的参数位于 request body 请求体中

因此,GET 请求的安全性不如 POST 请求,并且 GET 请求的参数有长度限制,而 POST 没有

3.2 Postman

 

http://t.weather.itboy.net/api/weather/city/101010100

  1. 点击"+"即可打开一个用于发送请求的标签页

  2. 选择请求的方法(GET / POST),并输入请求的URL

  3. 点击 【Params】,可以添加参数

  4. 点击 【Headers】, 可以添加请求头

  5. 点击 【Send】,发送该请求到服务器

  6. Body ,响应体,也就是服务器返回的实际数据,响应体中选择右侧的【JSON】格式,并选择【Pretty】,可以对 JSON数据做美化显示

  7. Headers ,响应头,也就是服务器返回的响应体数据

3.3 Qt实现HTTP请求

Qt框架提供了专门的类,方便实现HTTP请求

3.3.1 创建“网络访问管理”对象

首先需要创建一个 QNetworkAccessManger 对象,这是 Qt中进行 HTTP请求的开端

mNetAccessManager = new QNetworkAccessManger(this);

3.3.2 关联信号槽

在发送HTTP请求之前,先关联信号槽

//获取到数据之后
connect(mNetAccessManager,&QNetworkAccessManger::finished,this,&MainWindow::onReplied);

当请求结束,获取到服务器的数据时,mNetAccessManager会发射一个 finished 信号,该信号携带一个 QNetworkReply 的参数

服务器返回的所有数据就封装在其中,通过 QNetworkReply 类提供的各种方法,就可以获取响应头,响应体等各种数据

3.3.3 发送请求

发送 HTTP请求

QUrl	url("http://t.weather.itboy.net/api/weather/city/101010100");
mNetAccessManager->get(QNetworkRequest(url));

根据请求的地址构建出一个 QUrl对象,然后直接调用 QNetworkAccessManager 的 get方法,即可发送一个 GET请求

3.3.4 接收数据

由于上面绑定了信号槽,服务器返回数据后,自动调用我们自定义的槽函数 onReplied

onReplied函数的标准写法:

void MainWindow::onReplied(QNetworkReply* reply)
{
	//响应的状态码为200,表示请求成功
	int status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
	
	qDebug() << "operation:" << reply->operation();			//请求方式
	qDebug() << "status code:" << status_code;				//状态码
	qDebug() << "url:" <<reply->url();						//url
	qDebug() << "raw header:" <<relpy->rawHeaderList();		//header
	
	if(reply->error() != QNetworkReply::NoError || status_code != 200){
		QMessageBox::warning(this,"提示","请求数据失败!",QMessageBox::Ok);
	}
	else{
		//获取响应信息
		QByteArray reply_data = reply->readAll();
		QByteArray byteArray = QString(reply_data).toUtf8();
		qDebug() << "read all:" << byteArray.data();
		
		//parseJson();			//解析JSON	
	}
	reply->deleteLater();
}

可见,QNetworkReply中封装了服务器返回的所有数据,包括响应体,响应的状态码,响应体等

4.详细实现步骤

4.1新建工程,右键退出

4.1.1 创建工程

新建工程命名为Weather

基类为QMainWindow

添加资源文件,images.qrc,把res资源添加到工程目录下

4.1.2 设置窗口大小,无边框

在ui文件中,设置固定大小800*450

//mainwindow.cpp
//构造函数下:
    setWindowFlag(Qt::FramelessWindowHint);             //设置窗口无边框
    setFixedSize(width(),height());                     //设置固定窗口大小

4.1.3 实现右键退出

重写父类的菜单事件,默认上下文菜单事件虚函数,是不会进行任何操作的

//mainwindow.h
#include<QContextMenuEvent>

protected:
    void contextMenuEvent(QContextMenuEvent *event);

private:
    QMenu *mExitMenu;       //右键退出的菜单
    QAction *mExitAct;      //退出的行为-菜单项
//mainwindow.cpp
#include <QCursor>

//构造函数下:
    //构建右键菜单
    mExitMenu =new QMenu(this);
    mExitAct =new QAction();

    mExitAct->setText("退出");                           //设置菜单项名称为退出
    mExitAct->setIcon(QIcon(":/res/close.png"));        //设置菜单项图标

    mExitMenu->addAction(mExitAct);                     //把菜单项添加到菜单中

    connect(mExitAct,&QAction::triggered,this,[=](){	//连接槽函数,实现点击即退出
        qApp->exit(0);
    });
    
void MainWindow::contextMenuEvent(QContextMenuEvent *event)     //重写父类的contextMenuEvent虚函数,其父类中默认是忽略右键菜单事件
{
    //弹出右键菜单
    mExitMenu->exec(QCursor::pos());        //跟踪鼠标,在鼠标右键的地方弹出这个菜单项
    
    event->accept();                        //调用accept,表示这个事件已经处理,不需要向上传递
}

 

4.2界面布局

整体布局

 

 

在 centralWidget 下再添加一个 widget,之后所有控件都是放在这个 widget窗口下

4.2.1 设置窗口背景

设置 widget样式表:

QWidget#widget{
	border-image: url(:/res/background.png);
}

QLabel {
	font: 25 10pt "微软雅黑";
	border-radius: 4px;
	background-color: rgba(60, 60, 60, 100);
	color: rgb(255, 255, 255);
}

​​​​​​​

  • 整个窗口的背景就设置为资源文件中的 background.png

  • widget 窗口中的所有标签的字体、边框圆角、背景色、前景色

  • 如果要单独设置某个标签的样式,直接将要设置的样式,设置到对应标签上即可

4.2.2 标签显示图片

设置标签显示图片:

  • 属性窗口

  • 代码动态设置图片

一开始默认写死,先看到基本框架,后面导入数据再进行动态设置

​​​​​​​

4.2.3 修改控件名称

见名知义原则.

 

lblGanMao感冒指数显示(默认QLabel)
lblWindIcon今天风向的图标 (固定)
lblWindFl、lblWindFx今天风 的风力等级风 的风向
lblShiDuIcon湿度图标 (固定)
lblShiDu、lblShiDuTitle湿度文本 (固定)、湿度数值显示
lblPm25IconPM2.5 图标 (固定)
lblPm25、lblPm25TitlePM2.5文本(固定)、PM2.5数值显示
lblQualityIcon今天空气质量图标 (固定)
lblQuality、lblQualityTitle今天空气质量文本(固定)、空气质量值显示
lblDate0日期显示,标号从0到5可以显示6天
lblWeek0星期显示,今天明天星期几,标号从0到5
lblType0天气的文本,晴雨等,标号0到5
lblTypeIcon0天气的图标,和标号一一对应
lblHighCurve折线图,最高温
lblLowCurve
lblFl0风力等级 标号0到5
lblFx0风向风向 标号0到5
lblQuality0空气质量 标号0到5
lblTypeIcon今天的天气图标
lblLowHigh同上今天温度的最低到最高温度,19°~31°
lblType今天天气的文本,晴转多云
lblTemp当前温度,32°
lblCity当前城市
btnSearch搜索按钮,QPushButton
lblDate当前日期
leCity选择城市,QLineEdit

 

4.3 窗口移动,系统托盘

4.3.1 重写父类的鼠标移动事件

 

//mainwindow.h
#include<QMouseEvent>

protected:
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    
private:
    QPoint mOffset;         //窗口移动时,鼠标与窗口左上角的偏移
//mainwindow.cpp
//重写父类的鼠标事件
void MainWindow::mousePressEvent(QMouseEvent *event)
{
    mOffset =event->globalPos() - this->pos();          //偏移量=鼠标点击处距离桌面左上角的距离-窗口左上角距离桌面左上角的距离
}

void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
    this->move(event->globalPos() - mOffset);           //设置窗口移动到 (当前鼠标点击处距离桌面左上角的距离 - 偏移量)的位置
4.3.2设置系统托盘

声明系统托盘动作及初始化操作

//mainwindow.h
#include<QSystemTrayIcon>
private:
	//系统托盘
    QSystemTrayIcon *mysystemTray;
    void systemtrayiconActivated(QSystemTrayIcon::ActivationReason reason);     //响应系统托盘的动作(双击操作)
    void initsystemtrayIcon();          //系统托盘的初始化操作

相应实现

//mainwindow.cpp

//系统托盘
void MainWindow::systemtrayiconActivated(QSystemTrayIcon::ActivationReason reason)      //响应系统托盘的动作(双击操作)
{
    switch(reason){
    case QSystemTrayIcon::DoubleClick:      //双击
        //显示  隐藏  界面
        if(isHidden())
        {
            show();
        }
        else
        {
            hide();
        }
        break;
    default:
        break;
    }
}

void MainWindow::initsystemtrayIcon()       //系统托盘初始化操作
{
    mysystemTray =new QSystemTrayIcon(this);
    mysystemTray->setIcon(QIcon(":/res/WeaFor.png"));       //设置图标
    connect(mysystemTray,&QSystemTrayIcon::activated,this,&MainWindow::systemtrayiconActivated);

    //添加应该退出应用程序菜单
    QAction  *actionsystemquit =new QAction(QIcon(":/res/close.png"),u8"退出程序");     //创建一个功能--退出程序以及它的图标
    connect(actionsystemquit,&QAction::triggered,this,[=](){
        qApp->exit(0);
    });

    QMenu *pcontextmenu=new QMenu(this);        //创建菜单项
    pcontextmenu->addAction(actionsystemquit);      //给这个菜单项增加退出程序的功能
    mysystemTray->setContextMenu(pcontextmenu);     //把这个菜单项加入系统托盘
    mysystemTray->show();

}

在构造函数中 调用初始化函数

//mainwindow.cpp
//构造函数下:
{
	initsystemtrayIcon();       //系统托盘的初始化操作
}

4.4 HTTP请求天气数据

4.4.1 添加网络模块
QT       += core gui network

4.4.2 声明网络对象,槽函数

在头文件中,声明用于 http通信的 QNetworkAccessManager 指针对象

用于处理http服务返回数据的onReplied 槽函数

//mainwindow.h
#include<QNetworkAccessManager>
#include<QNetworkReply>

private slots:
    void onReplied(QNetworkReply *reply);		//onReplied槽函数,当获取到网络请求的数据,就调用
private:
    QNetworkAccessManager *mNetAccessManager;	//指针对象
4.4.3 关联信号槽

QNetworkAccessManager 的 finished信号,与自定义的 onReplied槽函数进行关联

//mainwindow.cpp

//构造函数下
{
    //http通信
    mNetAccessManager =new QNetworkAccessManager(this);
    connect(mNetAccessManager,&QNetworkAccessManager::finished,this,&MainWindow::onReplied);
}
4.4.4 发送http请求

声明用于发送http请求的成员函数

//mainwindow.h
protected:
    void getWeatherInfo(QString cityCode);

然后,调用getWeatherInfo 来发送http请求

//mainwindow.cpp

#include<QUrl>
//构造函数下:
{
   //直接在构造中,请求天气数据,(前期测试)
    getWeatherInfo("101010100");        //北京城市编码
}

void MainWindow::getWeatherInfo(QString cityCode)       //获取天气数据
{
    QUrl url("http://t.weather.itboy.net/api/weather/city/"+cityCode);
    mNetAccessManager->get(QNetworkRequest(url));		//请求数据  ,请求成功就会调用onReplied
}

4.4.5 接收服务端数据

当http请求完毕,服务器返回数据时,mNetAccessManager 就会发射一个finished 信号,进而调用onReplied槽函数

服务器返回的所有数据,都封装在QNetworkReply中,包括响应头,状态码,响应体等

//mainwindow.cpp

#include<QDebug>
#include<QMessageBox>

void MainWindow::onReplied(QNetworkReply *reply)         //数据处理
{
    qDebug()<<"onReplied success";

    //响应状态码为200,表示请求成功
    int status_code=reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
    qDebug()<<"operation: "<<reply->operation();			//请求方式
    qDebug()<<"status code: "<<status_code;					//状态码  
    qDebug()<<"url: "<<reply->url();						//url
    qDebug()<<"raw header"<<reply->rawHeaderList();			//header
    
	//如果指定的城市编码不存在,就会报错
	//"Error transfering  http://t.weather.itboy.net/api/weather/city/000000000 -server replied: Not Found"
    if(reply->error()!= QNetworkReply::NoError || status_code !=200){
        qDebug("%s(%d) error:%s",__FUNCTION__,__LINE__,reply->errorString().toLatin1().data());
        QMessageBox::warning(this,"天气","请求数据失败!",QMessageBox::Ok);
    }
    else{
        //获取响应信息
        QByteArray byteArray=reply->readAll();
        qDebug()<<"readAll:"<<byteArray.data();
        //parseJson(byteArray);
    }
    
    reply->deleteLater();

}

4.5 解析天气数据

4.5.1 定义两个类

在界面上主要显示的是今天天气和最近六天的天气,所以创建一个weatherdata.h,并定义两个类

4.5.1.1 Today

用于显示今天的所有天气参数,也就是屏幕左侧的数据

4.5.1.2 Day

用于显示六天的天气参数,就是屏幕右侧的数据

进而方便将解析出的数据保存到类的成员变量中

//weatherdata.h
#include<QString>

class Today{
public:
    Today()
    {
        date = "2022-10-20";
        city = "广州";
        
        ganmao = "感冒指数";
        
        wendu = 0;
        shidu= "0%";
        pm25 = 0;
        quality = "无数据";
        
        type= "多云";
        
        fl = "2级";
        fx = "南风";
        
        high = 30;
        low =18;
    }
    
    QString date;
    QString city;
    
    QString ganmao;
    
    int wendu;
    QString shidu;
    int pm25;
    QString quality;
    
    QString type;
    
    QString fx;
    QString fl;
    
    int high;
    int low;
};

class Day{
public:
    Day()
    {
        date = "2022-10-20";
        week = "周五";
        
        type = "多云";
        
        high = 0;
        low = 0;
        
        fx = "南风";
        fl = "2级";
        
        api =0 ;
    }
    QString date;
    QString week;
    
    QString type;
    
    int high;
    int low;
    
    QString fx;
    QString fl;
    
    int api; 
};

4.5.2 解析数据

声明解析的成员函数parseJson , 并且声明两个类 用于存放数据

//mainwindow.h

#include"weatherdata.h"
protected: 
    void parseJson(QByteArray& byteArray);  //解析

private:
    //将获取的数据放入这两个类中,方便后续展示
    Today mToday;
    Day mDay[6];

获取得到的json数据,在解析工具中 可以看到,整体的数据:

具体实现parseJson

//mainwindow.cpp

#include<QJsonDocument>
#include<QJsonObject>
#include<QJsonArray>

void MainWindow::parseJson(QByteArray &byteArray)       //数据解析
{
    QJsonParseError err;
    QJsonDocument doc =QJsonDocument::fromJson(byteArray,&err);     //如果出错会把出错信息存到err中
    if(err.error != QJsonParseError::NoError)           //如果err不等于NoError 就出错退出
    {
        return;
    }

    //开始解析
    QJsonObject rootObj  =doc.object();
    qDebug()<<rootObj.value("message").toString();

    //1.解析日期和城市
    mToday.date=rootObj.value("date").toString();                                   //解析出date
    mToday.city=rootObj.value("cityInfo").toObject().value("city").toString();      //先获取到json对象cityInfo 在在其中获取到city

    //2.解析yesterday
    QJsonObject objData=rootObj.value("data").toObject();
    QJsonObject objYesterday = objData.value("yesterday").toObject();       //先获取到json对象data,在去获取json对象yesterday

    mDay[0].week =objYesterday.value("week").toString();        //mDay是0-5的数组,其中对应yesterday的是第0元素
    mDay[0].date =objYesterday.value("ymd").toString();

    mDay[0].type =objYesterday.value("type").toString();

        //最高温最低温        高温低温读取到的数据是 "高温 18℃" 存在中文字符空格,这里只要获取其中的数字其中℃占一个字符
    QString s;
    s=objYesterday.value("high").toString().split(" ").at(1);       //获得高温字段的字符串,之后用split进行分割,分割依据是空格,取后面的数据,即01中的1,就可以取得18℃
    s=s.left(s.length()-1);       //将s缩短长度,即利用left方法,把s的长度-1就可以获得前面的18了
    //s=s.left(2);                //left方法:从左到右获取count个字符
    mDay[0].high=s.toInt();     //把s转为int型赋给high

    s=objYesterday.value("low").toString().split(" ").at(1);       //18℃
    s=s.left(s.length()-1);       //18
    //s=s.left(2);
    mDay[0].low=s.toInt();

        //风向风力
    mDay[0].fx=objYesterday.value("fx").toString();
    mDay[0].fl=objYesterday.value("fl").toString();

        //污染指数 aqi
    mDay[0].aqi=objYesterday.value("aqi").toDouble();

    //3.解析forcast中五天的数据
    QJsonArray forecastArr=objData.value("forecast").toArray();     //forecast 是一个json数组

    for(int i=0;i<5;i++)        //获取五天的数据,从今天开始的五天,不包括昨天
    {
        QJsonObject objForecast=forecastArr[i].toObject();      //获取forecast中的josn对象,进行赋值

        //不获取昨天的数据 所以应该是从1开始,所以参数应该是i+1开始
        mDay[i+1].week=objForecast.value("week").toString();
        mDay[i+1].date=objForecast.value("ymd").toString();

        //天气类型
        mDay[i+1].type=objForecast.value("type").toString();

        //高温低温
        QString s;
        s=objForecast.value("high").toString().split(" ").at(1);    //18℃
        s=s.left(s.length()-1);       //18
        //s=s.left(2);
        mDay[i+1].high=s.toInt();

        s=objForecast.value("low").toString().split(" ").at(1);    //18℃
        s=s.left(s.length()-1);       //18
        //s=s.left(2);
        mDay[i+1].low=s.toInt();

        //风向风力
        mDay[i+1].fx=objForecast.value("fx").toString();
        mDay[i+1].fl=objForecast.value("fl").toString();

        //污染指数 aqi
        mDay[i+1].aqi=objForecast.value("aqi").toDouble();

    }

    //4.解析今天的数据 用mToday去存储
    mToday.ganmao=objData.value("ganmao").toString();

    mToday.wendu=objData.value("wendu").toString().toInt();
    mToday.shidu=objData.value("shidu").toString();
    mToday.pm25=objData.value("pm25").toDouble();  //pm2.5原先就是int类型,转为dobule类型即可,不可以过String
    mToday.quality=objData.value("quality").toString();

    //5.forcast中第一个数组元素,也是今天的数据
    //今天的数据也属于五天之中的数据,所以要把今天的数据mToday也给到mDay
    mToday.type = mDay[1].type;

    mToday.fx = mDay[1].fx;
    mToday.fl = mDay[1].fl;

    mToday.high = mDay[1].high;
    mToday.low = mDay[1].low;


    //6.更新UI
    updateUI();
}

onReplied中调用parseJson

//mainwindow.cpp
void MainWindow::onReplied(QNetworkReply *reply)        //数据处理
{
	...
    else{
        //获取响应信息
        QByteArray byteArray=reply->readAll();
        qDebug()<<"readAll:"<<byteArray.data();
        
        parseJson(byteArray);							///调用parseJson解析数据			
    }
	....
}

4.6 更新UI界面

4.6.1 创建控件数组

六天的UI控件,显示语句基本相同,所以可以使用循环来写,这时候就需要将这些控件放入相应的控件数组进行控制

//mainwindow.h
#include<QLabel>

private:
    //创建一些控件数组
    //星期和日期
    QList<QLabel*> mWeekList;
    QList<QLabel*> mDateList;
    //天气和天气图标
    QList<QLabel*> mTypeList;
    QList<QLabel*> mTypeIconList;
    //天气污染指数
    QList<QLabel*> mAqiList;
    //风力风向
    QList<QLabel*> mFxList;
    QList<QLabel*> mFlList;

在mainwindow.cpp中初始化列表

//mainwindow.cpp

//构造函数下:
{
    //4.UI初始化
    //将控件添加到控件数组,方便循环处理
    //星期和日期
    mWeekList<<ui->lblWeek0<<ui->lblWeek1<<ui->lblWeek2<<ui->lblWeek3<<ui->lblWeek4<<ui->lblWeek5;
    mDateList<<ui->lblDate0<<ui->lblDate1<<ui->lblDate2<<ui->lblDate3<<ui->lblDate4<<ui->lblDate5;
    //天气和天气图标
    mTypeList<<ui->lblType0<<ui->lblType1<<ui->lblType2<<ui->lblType3<<ui->lblType4<<ui->lblType5;
    mTypeIconList<<ui->lblTypeIcon0<<ui->lblTypeIcon1<<ui->lblTypeIcon2<<ui->lblTypeIcon3<<ui->lblTypeIcon4<<ui->lblTypeIcon5;
    //天气指数
    mAqiList<<ui->lblQuality0<<ui->lblQuality1<<ui->lblQuality2<<ui->lblQuality3<<ui->lblQuality4<<ui->lblQuality5;
    //风向和风力
    mFxList<<ui->lblFx0<<ui->lblFx1<<ui->lblFx2<<ui->lblFx3<<ui->lblFx4<<ui->lblFx5;
    mFlList<<ui->lblFl0<<ui->lblFl1<<ui->lblFl2<<ui->lblFl3<<ui->lblFl4<<ui->lblFl5;
}

4.6.2 更新UI
//mainwindow.h
protected:
    void updateUI();                        //更新UI
//mainwindow.cpp
void MainWindow::updateUI()
{
    //1.更新日期,右上角
    //ui->lblDate->setText(mToday.date)         //直接获取json的数据 得到的日期是20220210的格式,做一个转换
    ui->lblDate->setText(QDateTime::fromString(mToday.date,"yyyyMMdd").toString("yyyy/MM/dd")+" "+mDay[1].week);    //显示右上角的日期+星期

    //2.更新城市,右边 城市框
    ui->lblCity->setText(mToday.city);

    //3.更新今天数据  左侧今天的数据
    ui->lblTemp->setText(QString::number(mToday.wendu));            //温度
    ui->lblType->setText(mToday.type);                              //天气文本
    ui->lblLowHigh->setText(QString::number(mToday.low)+"~"+QString::number(mToday.high)+"°C");     //最低温到最高温

    ui->lblGanMao->setText("感冒指数: "+mToday.ganmao);               //感冒指数
    ui->lblWindFx->setText(mToday.fx);                              //风向
    ui->lblWindFl->setText(mToday.fl);                              //风力

    ui->lblPM25->setText(QString::number(mToday.pm25));             //PM2.5

    ui->lblShiDu->setText(mToday.shidu);                            //湿度
    ui->lblQuality->setText(mToday.quality);                        //空气质量

    //4.更新六天数据,右侧界面
    for(int i=0;i<6;i++)
    {
        //4.1更新日期和时间
        mWeekList[i]->setText("周"+mDay[i].week.right(1));           //转换格式将星期几 转成周几
        ui->lblWeek0->setText("昨天");                                //将前三天固定写为昨天今天明天
        ui->lblWeek1->setText("今天");
        ui->lblWeek2->setText("明天");

        QStringList ymdList=mDay[i].date.split("-");                //json数据日期格式为:2023-02-18    以"-"为分割依据,将其分为3部分012
        mDateList[i]->setText(ymdList[1]+"/"+ymdList[2]);           //取ymdList的12部分,即02/18

        //4.2更新天气类型
        mTypeList[i]->setText(mDay[i].type);

        //4.3更新空气质量
        //对aqi进行判断,赋予相应等级
        if(mDay[i].aqi >= 0 && mDay[i].aqi <= 50)
        {
            mAqiList[i]->setText("优");                              //0~50  优
            mAqiList[i]->setStyleSheet("background-color: rgb(121, 184, 0);");      //设置风格样式,改变颜色
        }
        else if(mDay[i].aqi >= 50 && mDay[i].aqi <= 100)
        {
            mAqiList[i]->setText("良");                              //50~100  良
            mAqiList[i]->setStyleSheet("background-color: rgb(255, 187, 23);");      //设置风格样式,改变颜色
        }
        else if(mDay[i].aqi >= 100 && mDay[i].aqi <= 150)
        {
            mAqiList[i]->setText("轻度");                              //100~150  轻度
            mAqiList[i]->setStyleSheet("background-color: rgb(255, 87, 97);");      //设置风格样式,改变颜色
        }
        else if(mDay[i].aqi >= 150 && mDay[i].aqi <= 200)
        {
            mAqiList[i]->setText("中度");                              //150~200  中度
            mAqiList[i]->setStyleSheet("background-color: rgb(235, 17, 27);");      //设置风格样式,改变颜色
        }
        else if(mDay[i].aqi >= 200 && mDay[i].aqi <= 250)
        {
            mAqiList[i]->setText("重度");                              //200~250  重度
            mAqiList[i]->setStyleSheet("background-color: rgb(170, 0, 0);");      //设置风格样式,改变颜色
        }
        else
        {
            mAqiList[i]->setText("严重");                              //大于250  严重
            mAqiList[i]->setStyleSheet("background-color: rgb(110, 0, 0);");      //设置风格样式,改变颜色
        }
        
        //4.4 更新风力风向
        mFxList[i]->setText(mDay[i].fx);
        mFlList[i]->setText(mDay[i].fl);
    }
}

在 parseJson解析处理中 调用updateUI

//mainwindow.cpp
void MainWindow::parseJson(QByteArray &byteArray)       //数据解析
{
    //6.更新UI
    updateUI();
}

4.6.2.1天气图标处理
//mainwindow.h
private:
   //天气图标
    QMap<QString,QString> mTypeMap;     //用于英文到中文的转换
//mainwindow.cpp
//构造函数下:
{
    //天气图标设置
    //以天气类型作为key,以资源路径作为value,初始化MAP
    mTypeMap.insert("暴雪",":/res/type/BaoXue.png");
    mTypeMap.insert("暴雨",":/res/type/BaoYu.png");
    mTypeMap.insert("暴雨到大暴雨",":/res/type/BaoYuDaoDaBaoYu.png");
    mTypeMap.insert("大暴雨",":/res/type/DaBaoYu.png");
    mTypeMap.insert("大暴雨到特大暴雨",":/res/type/DaBaoYuDaoTeDaBaoYu.png");
    mTypeMap.insert("大到暴雪",":/res/type/DaDaoBaoXue.png");
    mTypeMap.insert("大到暴雨",":/res/type/DaDaoBaoYu.png");
    mTypeMap.insert("大雪",":/res/type/DaXue.png");
    mTypeMap.insert("大雨",":/res/type/DaYu.png");
    mTypeMap.insert("冻雨",":/res/type/DongYu.png");
    mTypeMap.insert("多云",":/res/type/DuoYun.png");
    mTypeMap.insert("浮尘",":/res/type/FuChen.png");
    mTypeMap.insert("雷阵雨",":/res/type/LeiZhenYu.png");
    mTypeMap.insert("雷阵雨伴有冰雹",":/res/type/LeiZhenYuBanYouBingBao.png");
    mTypeMap.insert("霾",":/res/type/Mai.png");
    mTypeMap.insert("强沙尘暴",":/res/type/QiangShaChenBao.png");
    mTypeMap.insert("晴",":/res/type/Qing.png");
    mTypeMap.insert("沙尘暴",":/res/type/ShaChenBao.png");
    mTypeMap.insert("特大暴雨",":/res/type/TeDaBaoYu.png");
    mTypeMap.insert("undefined",":/res/type/undefined.png");
    mTypeMap.insert("雾",":/res/type/Wu.png");
    mTypeMap.insert("小到中雪",":/res/type/XiaoDaoZhongXue.png");
    mTypeMap.insert("小到中雨",":/res/type/XiaoDaoZhongYu.png");
    mTypeMap.insert("小雪",":/res/type/XiaoXue.png");
    mTypeMap.insert("小雨",":/res/type/XiaoYu.png");
    mTypeMap.insert("雪",":/res/type/Xue.png");
    mTypeMap.insert("扬沙",":/res/type/YangSha.png");
    mTypeMap.insert("阴",":/res/type/Yin.png");
    mTypeMap.insert("雨",":/res/type/Yu.png");
    mTypeMap.insert("雨夹雪",":/res/type/YuJiaXue.png");
    mTypeMap.insert("阵雪",":/res/type/ZhenXue.png");
    mTypeMap.insert("阵雨",":/res/type/ZhenYu.png");
    mTypeMap.insert("中到大雪",":/res/type/ZhongDaoDaXue.png");
    mTypeMap.insert("中到大雨",":/res/type/ZhongDaoDaYu.png");
    mTypeMap.insert("中雪",":/res/type/ZhongXue.png");
    mTypeMap.insert("中雨",":/res/type/ZhongYu.png");
}

返回继续处理更新UI界面的天气图标

//mainwindow.cpp
void MainWindow::updateUI()
{
	//3.更新今天数据  左侧今天的数据
	ui->lblTypeIcon->setPixmap(mTypeMap[mToday.type]);              //天气图标,以通过type做为key,进到mTypeMap去获得它的value 继而获得图片资源地址,在贴上去,进而更新图标
	
	//4更新六天数据
		//4.2更新天气类型
        mTypeIconList[i]->setPixmap(mTypeMap[mDay[i].type]);        //取到type作为key,到mTypeMap中找到value
		
}

4.7城市编码

4.7.1 创建一个工具类

创建一个weathertool.h

创建一个工具类,来通过城市名进而获取到9位城市编码

//weathertool.h
#include<QMap>
#include<QFile>

#include<QJsonArray>
#include<QJsonDocument>
#include<QJsonObject>
#include<QJsonParseError>
#include<QJsonValue>

class WeatherTool{
    
private:
    static QMap<QString,QString> mCityMap;      //声明一个静态成员,QMap
    
    static void initCityMap(){                  //初始化CityMap
        //1.读取文件
        QString filePath ="E:\\Project\\QT_Weather\\Qt_Weather\\citycode.json";
        
        QFile file(filePath);
        file.open(QIODevice::ReadOnly | QIODevice::Text);
        QByteArray json=file.readAll();
        file.close();
        
        //2.解析并写入map
        QJsonParseError err;                    
        QJsonDocument doc =QJsonDocument::fromJson(json,&err);         //如果解析错误,把错误信息存到err中
        if(err.error!= QJsonParseError::NoError)
        {
            return;
        }
        if(!doc.isArray())      //如果不是数组
        {
            return;
        }
        
        //开始解析
        QJsonArray cities=doc.array();      //把doc转换成QJsonArray格式
        for(int i=0;i<cities.size();i++)
        {
            QString city=cities[i].toObject().value("city_name").toString();
            QString code=cities[i].toObject().value("city_code").toString();
            
            //把获取到的json数据转换成QString格式然后存入QMap中
            if(code.size() > 0){                    //citycode.json文件当中,省份的city_code是空,不将其存入map
                mCityMap.insert(city,code); 
            }    
        }   
    }
    
public:
    static QString getCityCode(QString cityName){       //声明静态成员函数getCityCode,通过key:city_name值去获得value:city_code
        if(mCityMap.isEmpty()){                         //如果位空就初始化citymap
            initCityMap();
        }
        QMap<QString,QString>::iterator it =mCityMap.find(cityName);        //获取iterator,进而拿到key和value
        //输入北京/北京市
        if(it==mCityMap.end()){
            it =mCityMap.find(cityName + "市");          //在遍历寻找北京市
        }
        if(it!=mCityMap.end()){
            return it.value();
        }
        return "";
    }
};

QMap<QString,QString> WeatherTool::mCityMap={}; 

4.7.2 调用这个工具类
//mainwindow.cpp
#include "weathertool.h"
//构造函数下:
{
    //3.网络通信请求(http通信)
    //直接在构造中,请求天气数据,(前期测试)
    //getWeatherInfo("101010100");        //北京
    //getWeatherInfo("101280101");        //广州
    getWeatherInfo("广州");				//可以通过输入字符串 来进行查询
}

//将原先的获取天气数据函数进行修改
//void MainWindow::getWeatherInfo(QString cityCode)       //获取天气数据
//{
//    QUrl url("http://t.weather.itboy.net/api/weather/city/"+cityCode);
//    mNetAccessManager->get(QNetworkRequest(url));       //请求数据  ,请求成功就会调用onReplied
//}

/*---------------------------------------------修改如下-------------------------------------------------*/
void MainWindow::getWeatherInfo(QString cityName)       //获取天气数据
{
    QString cityCode=WeatherTool::getCityCode(cityName);        //通过getCityCode方法利用key:cityName去获取到cityCode

    //当输入城市错误时进行判断
    if(cityCode.isEmpty()){
        QMessageBox::warning(this,"天气","请检查输入是否正确!",QMessageBox::Ok);
        return;
    }

    QUrl url("http://t.weather.itboy.net/api/weather/city/"+cityCode);
    mNetAccessManager->get(QNetworkRequest(url));       //请求数据  ,请求成功就会调用onReplied
}

4.7.3 实现程序外设置城市

在ui界面中 搜索城市的按钮 转到槽

//mainwindow.cpp
void MainWindow::on_btnSearch_clicked()
{
    QString cityName =ui->leCity->text();
    getWeatherInfo(cityName);
}

4.8 绘制温度曲线

将6天的数据,使用曲线连接起来,使得天气趋势一目了然

具体流程:

  • 调用标签的update方法

  • 框架发送QEvent::Paint事件给标签

  • 事件被MainWindow拦截,进而调用其eventFilter方法

  • 在eventFilter中,调用paintHighCurvepaintLowCurve来绘制曲线

4.8.1 安装事件过滤器

位高低温的标签 lblHighCurvelblLowCurve 安装事件过滤器

//mainwindow.cpp
//构造函数下:
    //5.给标签添加事件过滤器
    //给高低温曲线添加事件过滤器,参数指定位this,也就是当前窗口对象MainWindow
    //指定了当前窗口,就给当前窗口重写eventFiter方法,当有标签的事件到来时候,当前窗口就会对它进行过滤,当前窗口捕获了这个事件之后,就可以在当前窗口进行绘制曲线
    ui->lblHighCurve->installEventFilter(this);     
    ui->lblLowCurve->installEventFilter(this);

4.8.2 重写eventFilter方法

重写MainWindow下的evenFilter方法

这样就可以在eventFilter方法中拦截发向lblHighCurve和lblLowCurve 这两个控件的事件

//mainwindow.h
protected: 
    bool eventFilter(QObject *watched,QEvent *event);       //重写父类的eventFilter方法
//mainwindow.cpp
bool MainWindow::eventFilter(QObject *watched, QEvent *event)       //事件过滤器,捕获事件
{
    //对绘制高低温曲线的控件进行监听,捕获其事件,以绘制曲线
    if(watched==ui->lblHighCurve && event->type()==QEvent::Paint){
        paintHighCurve();
    }
    if(watched==ui->lblLowCurve && event->type()==QEvent::Paint){
        paintLowCurve();
    }
    return QWidget::eventFilter(watched,event);
}

4.8.3 绘制曲线

具体方法:

首先,声明绘制高低温曲线的两个成员函数

//mainwindow.h
protected: 
    //绘制高低温曲线
    void paintHighCurve();
    void paintLowCurve();

具体绘画函数

//mainwindow.cpp
#include<QPainter>
#include<QPen>
#include<QPoint>
#include<QBrush>

#define INCREMENT 3     //温度每升高/降低1°,y轴坐标的增量
#define POINT_RADIUS 3  //曲线描点(空心圆)的大小
#define TEXT_OFFSET_X 12    //X轴偏移
#define TEXT_OFFSET_Y 12    //Y轴偏移

void MainWindow::paintHighCurve()
{
    QPainter painter(ui->lblHighCurve);     //绘画区域

    //抗锯齿
    painter.setRenderHint(QPainter::Antialiasing,true);     //抗锯齿效果,true是开关

    //1.获取x坐标
    int pointX[6] = {0};        //定义x坐标的数组
    for(int i=0;i<6;i++){
        pointX[i]=mWeekList[i]->pos().x()+mWeekList[i]->width()/2;  //x坐标=上方日期控件的中心位置=日期控件的坐标(左上角顶点)+控件宽度的一半,就是中心位置
    }

    //2.获取y坐标
    int tempSum = 0;
    int tempAverage=0;
    for(int i=0;i<6;i++){
        tempSum += mDay[i].high;        //对6天的高温进行求和
    }
    tempAverage=tempSum/6;          //最高温的平均值
    //图中显示的曲线,在lbael框中,中心位置应该为平均值,若高于平均值在其上方,低于在下方;高一度就向上移动3像素
        //计算y轴坐标
    int pointY[6]={0};              //定义y坐标的数组
    int yCenter =ui->lblHighCurve->height()/2;      //确定平均值所在的区域,为label区域高度一半的位置
    for(int i=0;i<6;i++){
        //Qt中 坐标计算,向下是+,向上是-
        pointY[i] =yCenter-((mDay[i].high-tempAverage) * INCREMENT);        //y坐标=中心值高度-(温度-平均值)*3像素      如果平均值16,当日温度17则y=h-(17-16)*3=h-3,即在中心位置向上移动3像素
    }

    //3.开始绘制
    //3.1初始化画笔相关工具
    QPen pen=painter.pen();                 //获取画笔
    pen.setWidth(1);                        //设置画笔的宽度
    pen.setColor(QColor(255,170,0));        //设置画笔的颜色

    painter.setPen(pen);                    //设置画笔
    painter.setBrush(QColor(255,170,0));    //设置画刷颜色,填充内部

    //3.2画点,文本
    for(int i=0;i<6;i++){
        //显示点
        painter.drawEllipse(QPoint(pointX[i],pointY[i]),POINT_RADIUS,POINT_RADIUS);         //绘制点(圆心坐标,xy半径(这里是xy的偏移)),但此时是空心圆,需要画刷填充

        //显示温度
        painter.drawText(pointX[i]-TEXT_OFFSET_X,pointY[i]-TEXT_OFFSET_Y,QString::number(mDay[i].high)+"°");        //绘制文本,使其位于点的上方,(绘制点坐标(要一定偏移,不然和点重合了),文本信息)
    }
    //3.3 连线    两点之间确定一条直线,一共绘制五次
    for(int i=0;i<5;i++){       //不能到6,因为素组是0-5的,如果i可以到5的话,pointX[i+1]就到6了,会越界
        if(i==0){
            pen.setStyle(Qt::DotLine);      //虚线
            painter.setPen(pen);
        }
        else{
            pen.setStyle(Qt::SolidLine);    //实线
            painter.setPen(pen);
        }
        painter.drawLine(pointX[i],pointY[i],pointX[i+1],pointY[i+1]);              //(x1,y1,x2,y2)
    }
}

void MainWindow::paintLowCurve()            //绘制低温曲线
{
    QPainter painter(ui->lblLowCurve);

    //抗锯齿
    painter.setRenderHint(QPainter::Antialiasing,true);

    //1.获取x坐标
    int pointX[6] = {0};
    for(int i=0;i<6;i++){
        pointX[i]=mWeekList[i]->pos().x()+mWeekList[i]->width()/2;
    }

    //2.获取y坐标
    int tempSum = 0;
    int tempAverage=0;
    for(int i=0;i<6;i++){
        tempSum += mDay[i].low;
    }
    tempAverage=tempSum/6;          //最高温的平均值

    //计算y轴坐标
    int pointY[6]={0};
    int yCenter =ui->lblLowCurve->height()/2;
    for(int i=0;i<6;i++){
        pointY[i] =yCenter-((mDay[i].low-tempAverage) * INCREMENT);
    }

    //3.开始绘制
    //3.1初始化画笔相关工具
    QPen pen=painter.pen();
    pen.setWidth(1);                        //设置画笔的宽度
    pen.setColor(QColor(0,255,255));        //设置画笔的颜色

    painter.setPen(pen);
    painter.setBrush(QColor(0,255,255));    //设置画刷颜色,填充内部

    //3.2画点,文本
    for(int i=0;i<6;i++){
        //显示点
        painter.drawEllipse(QPoint(pointX[i],pointY[i]),POINT_RADIUS,POINT_RADIUS);         //绘制点,但此时是空心圆,需要画刷填充

        //显示温度
        painter.drawText(pointX[i]-TEXT_OFFSET_X,pointY[i]-TEXT_OFFSET_Y,QString::number(mDay[i].low)+"°");        //绘制文本,使其位于点的上方,利用偏移
    }
    //3.3 连线
    for(int i=0;i<5;i++){       //不能到6,因为素组是0-5的,如果i可以到5的话,pointX[i+1]就到6了,会越界
        if(i==0){
            pen.setStyle(Qt::DotLine);      //虚线
            painter.setPen(pen);
        }
        else{
            pen.setStyle(Qt::SolidLine);    //实线
            painter.setPen(pen);
        }
        painter.drawLine(pointX[i],pointY[i],pointX[i+1],pointY[i+1]);
    }
}

手动刷新曲线

//mainwindow.cpp
void MainWindow::parseJson(QByteArray &byteArray)       //数据解析
{
    //6.2
    //绘制高温曲线
    ui->lblHighCurve->update();     //手动刷新
    ui->lblLowCurve->update();
}

4.9 完整代码

4.9.1 mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include<QContextMenuEvent>
#include<QMouseEvent>

#include<QNetworkAccessManager>
#include<QNetworkReply>

#include"weatherdata.h"
#include<QLabel>

#include<QSystemTrayIcon>



QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

protected:
    void contextMenuEvent(QContextMenuEvent *event);    //重写父类菜单事件
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);

    void getWeatherInfo(QString cityCode);  //获取天气数据

    void parseJson(QByteArray& byteArray);  //数据解析

    void updateUI();                        //更新UI

    bool eventFilter(QObject *watched,QEvent *event);       //重写父类的eventFilter方法
    //绘制高低温曲线
    void paintHighCurve();
    void paintLowCurve();




private slots:
    void onReplied(QNetworkReply *reply);   //当获取到网络请求的数据,就调用

    void on_btnSearch_clicked();

private:
    //右键退出
    QMenu *mExitMenu;       //右键退出的菜单
    QAction *mExitAct;      //退出的行为-菜单项

    //窗口移动
    QPoint mOffset;         //窗口移动时,鼠标与窗口左上角的偏移

    //http请求
    QNetworkAccessManager *mNetAccessManager;       //网络请求的私有成员变量

    //将获取的数据放入这两个类中,方便后续展示
    Today mToday;
    Day mDay[6];

    //创建一些控件数组
    //星期和日期
    QList<QLabel*> mWeekList;
    QList<QLabel*> mDateList;
    //天气和天气图标
    QList<QLabel*> mTypeList;
    QList<QLabel*> mTypeIconList;
    //天气污染指数
    QList<QLabel*> mAqiList;
    //风力风向
    QList<QLabel*> mFxList;
    QList<QLabel*> mFlList;

    //天气图标
    QMap<QString,QString> mTypeMap;     //用于英文到中文的转换


    //系统托盘
    QSystemTrayIcon *mysystemTray;
    void systemtrayiconActivated(QSystemTrayIcon::ActivationReason reason);     //响应系统托盘的动作(双击操作)
    void initsystemtrayIcon();          //系统托盘的初始化操作
    //void quitmusicplayer();             //退出应用程序



};
#endif // MAINWINDOW_H
4.9.2 mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QCursor>
#include<QUrl>
#include<QDebug>
#include<QMessageBox>

#include<QJsonDocument>
#include<QJsonObject>
#include<QJsonArray>

#include<QDateTime>

#include "weathertool.h"

#include<QPainter>
#include<QPen>
#include<QPoint>
#include<QBrush>

#define INCREMENT 3     //温度每升高/降低1°,y轴坐标的增量
#define POINT_RADIUS 3  //曲线描点(空心圆)的大小
#define TEXT_OFFSET_X 12    //X轴偏移
#define TEXT_OFFSET_Y 12    //Y轴偏移

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //1.设置窗口属性
    setWindowFlag(Qt::FramelessWindowHint);             //设置窗口无边框
    setFixedSize(width(),height());                     //设置固定窗口大小


    //2.构建右键菜单
    mExitMenu =new QMenu(this);
    mExitAct =new QAction();

    mExitAct->setText("退出");                           //设置菜单项名称为退出
    mExitAct->setIcon(QIcon(":/res/close.png"));        //设置菜单项图标

    mExitMenu->addAction(mExitAct);                     //把菜单项添加到菜单中

    connect(mExitAct,&QAction::triggered,this,[=](){	//连接槽函数,实现点击即退出
        qApp->exit(0);
    });


    //3.网络通信请求(http通信)
    mNetAccessManager =new QNetworkAccessManager(this);
    connect(mNetAccessManager,&QNetworkAccessManager::finished,this,&MainWindow::onReplied);

    //直接在构造中,请求天气数据,(前期测试)
    //getWeatherInfo("101010100");        //北京
    //getWeatherInfo("101280101");        //广州
    getWeatherInfo("广州");

    //4.UI初始化
    //将控件添加到控件数组,方便循环处理
    //星期和日期
    mWeekList<<ui->lblWeek0<<ui->lblWeek1<<ui->lblWeek2<<ui->lblWeek3<<ui->lblWeek4<<ui->lblWeek5;
    mDateList<<ui->lblDate0<<ui->lblDate1<<ui->lblDate2<<ui->lblDate3<<ui->lblDate4<<ui->lblDate5;
    //天气和天气图标
    mTypeList<<ui->lblType0<<ui->lblType1<<ui->lblType2<<ui->lblType3<<ui->lblType4<<ui->lblType5;
    mTypeIconList<<ui->lblTypeIcon0<<ui->lblTypeIcon1<<ui->lblTypeIcon2<<ui->lblTypeIcon3<<ui->lblTypeIcon4<<ui->lblTypeIcon5;
    //天气指数
    mAqiList<<ui->lblQuality0<<ui->lblQuality1<<ui->lblQuality2<<ui->lblQuality3<<ui->lblQuality4<<ui->lblQuality5;
    //风向和风力
    mFxList<<ui->lblFx0<<ui->lblFx1<<ui->lblFx2<<ui->lblFx3<<ui->lblFx4<<ui->lblFx5;
    mFlList<<ui->lblFl0<<ui->lblFl1<<ui->lblFl2<<ui->lblFl3<<ui->lblFl4<<ui->lblFl5;

    //天气图标设置
    //以天气类型作为key,以资源路径作为value,初始化MAP
    mTypeMap.insert("暴雪",":/res/type/BaoXue.png");
    mTypeMap.insert("暴雨",":/res/type/BaoYu.png");
    mTypeMap.insert("暴雨到大暴雨",":/res/type/BaoYuDaoDaBaoYu.png");
    mTypeMap.insert("大暴雨",":/res/type/DaBaoYu.png");
    mTypeMap.insert("大暴雨到特大暴雨",":/res/type/DaBaoYuDaoTeDaBaoYu.png");
    mTypeMap.insert("大到暴雪",":/res/type/DaDaoBaoXue.png");
    mTypeMap.insert("大到暴雨",":/res/type/DaDaoBaoYu.png");
    mTypeMap.insert("大雪",":/res/type/DaXue.png");
    mTypeMap.insert("大雨",":/res/type/DaYu.png");
    mTypeMap.insert("冻雨",":/res/type/DongYu.png");
    mTypeMap.insert("多云",":/res/type/DuoYun.png");
    mTypeMap.insert("浮尘",":/res/type/FuChen.png");
    mTypeMap.insert("雷阵雨",":/res/type/LeiZhenYu.png");
    mTypeMap.insert("雷阵雨伴有冰雹",":/res/type/LeiZhenYuBanYouBingBao.png");
    mTypeMap.insert("霾",":/res/type/Mai.png");
    mTypeMap.insert("强沙尘暴",":/res/type/QiangShaChenBao.png");
    mTypeMap.insert("晴",":/res/type/Qing.png");
    mTypeMap.insert("沙尘暴",":/res/type/ShaChenBao.png");
    mTypeMap.insert("特大暴雨",":/res/type/TeDaBaoYu.png");
    mTypeMap.insert("undefined",":/res/type/undefined.png");
    mTypeMap.insert("雾",":/res/type/Wu.png");
    mTypeMap.insert("小到中雪",":/res/type/XiaoDaoZhongXue.png");
    mTypeMap.insert("小到中雨",":/res/type/XiaoDaoZhongYu.png");
    mTypeMap.insert("小雪",":/res/type/XiaoXue.png");
    mTypeMap.insert("小雨",":/res/type/XiaoYu.png");
    mTypeMap.insert("雪",":/res/type/Xue.png");
    mTypeMap.insert("扬沙",":/res/type/YangSha.png");
    mTypeMap.insert("阴",":/res/type/Yin.png");
    mTypeMap.insert("雨",":/res/type/Yu.png");
    mTypeMap.insert("雨夹雪",":/res/type/YuJiaXue.png");
    mTypeMap.insert("阵雪",":/res/type/ZhenXue.png");
    mTypeMap.insert("阵雨",":/res/type/ZhenYu.png");
    mTypeMap.insert("中到大雪",":/res/type/ZhongDaoDaXue.png");
    mTypeMap.insert("中到大雨",":/res/type/ZhongDaoDaYu.png");
    mTypeMap.insert("中雪",":/res/type/ZhongXue.png");
    mTypeMap.insert("中雨",":/res/type/ZhongYu.png");


    //5.给标签添加事件过滤器
    //给高低温曲线添加事件过滤器,参数指定位this,也就是当前窗口对象MainWindow
    //指定了当前窗口,就给当前窗口重写eventFiter方法,当有标签的事件到来时候,当前窗口就会对它进行过滤,当前窗口捕获了这个事件之后,就可以在当前窗口进行绘制曲线
    ui->lblHighCurve->installEventFilter(this);
    ui->lblLowCurve->installEventFilter(this);

    initsystemtrayIcon();       //系统托盘的初始化操作

}


MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::contextMenuEvent(QContextMenuEvent *event)     //重写父类的contextMenuEvent虚函数,其父类中默认是忽略右键菜单事件
{
    //弹出右键菜单
    mExitMenu->exec(QCursor::pos());        //跟踪鼠标,在鼠标右键的地方弹出这个菜单项

    event->accept();                        //调用accept,表示这个事件已经处理,不需要向上传递
}

void MainWindow::mousePressEvent(QMouseEvent *event)
{
    mOffset =event->globalPos() - this->pos();          //偏移量=鼠标点击处距离桌面左上角的距离-窗口左上角距离桌面左上角的距离
}

void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
    this->move(event->globalPos() - mOffset);           //设置窗口移动到 (当前鼠标点击处距离桌面左上角的距离 - 偏移量)的位置
}


//void MainWindow::getWeatherInfo(QString cityCode)       //获取天气数据
//{
//    QUrl url("http://t.weather.itboy.net/api/weather/city/"+cityCode);
//    mNetAccessManager->get(QNetworkRequest(url));       //请求数据  ,请求成功就会调用onReplied
//}
void MainWindow::getWeatherInfo(QString cityName)       //获取天气数据
{
    QString cityCode=WeatherTool::getCityCode(cityName);        //通过getCityCode方法利用key:cityName去获取到cityCode

    //当输入城市错误时进行判断
    if(cityCode.isEmpty()){
        QMessageBox::warning(this,"天气","请检查输入是否正确!",QMessageBox::Ok);
        return;
    }

    QUrl url("http://t.weather.itboy.net/api/weather/city/"+cityCode);
    mNetAccessManager->get(QNetworkRequest(url));       //请求数据  ,请求成功就会调用onReplied
}


void MainWindow::parseJson(QByteArray &byteArray)       //数据解析
{
    QJsonParseError err;
    QJsonDocument doc =QJsonDocument::fromJson(byteArray,&err);     //如果出错会把出错信息存到err中
    if(err.error != QJsonParseError::NoError)           //如果err不等于NoError 就出错退出
    {
        return;
    }

    //开始解析
    QJsonObject rootObj  =doc.object();
    qDebug()<<rootObj.value("message").toString();

    //1.解析日期和城市
    mToday.date=rootObj.value("date").toString();                                   //解析出date
    mToday.city=rootObj.value("cityInfo").toObject().value("city").toString();      //先获取到json对象cityInfo 在在其中获取到city

    //2.解析yesterday
    QJsonObject objData=rootObj.value("data").toObject();
    QJsonObject objYesterday = objData.value("yesterday").toObject();       //先获取到json对象data,在去获取json对象yesterday

    mDay[0].week =objYesterday.value("week").toString();        //mDay是0-5的数组,其中对应yesterday的是第0元素
    mDay[0].date =objYesterday.value("ymd").toString();

    mDay[0].type =objYesterday.value("type").toString();

        //最高温最低温        高温低温读取到的数据是 "高温 18℃" 存在中文字符空格,这里只要获取其中的数字其中℃占一个字符
    QString s;
    s=objYesterday.value("high").toString().split(" ").at(1);       //获得高温字段的字符串,之后用split进行分割,分割依据是空格,取后面的数据,即01中的1,就可以取得18℃
    s=s.left(s.length()-1);       //将s缩短长度,即利用left方法,把s的长度-1就可以获得前面的18了
    //s=s.left(2);                //left方法:从左到右获取count个字符
    mDay[0].high=s.toInt();     //把s转为int型赋给high

    s=objYesterday.value("low").toString().split(" ").at(1);       //18℃
    s=s.left(s.length()-1);       //18
    //s=s.left(2);
    mDay[0].low=s.toInt();

        //风向风力
    mDay[0].fx=objYesterday.value("fx").toString();
    mDay[0].fl=objYesterday.value("fl").toString();

        //污染指数 aqi
    mDay[0].aqi=objYesterday.value("aqi").toDouble();

    //3.解析forcast中五天的数据
    QJsonArray forecastArr=objData.value("forecast").toArray();     //forecast 是一个json数组

    for(int i=0;i<5;i++)        //获取五天的数据,从今天开始的五天,不包括昨天
    {
        QJsonObject objForecast=forecastArr[i].toObject();      //获取forecast中的josn对象,进行赋值

        //不获取昨天的数据 所以应该是从1开始,所以参数应该是i+1开始
        mDay[i+1].week=objForecast.value("week").toString();
        mDay[i+1].date=objForecast.value("ymd").toString();

        //天气类型
        mDay[i+1].type=objForecast.value("type").toString();

        //高温低温
        QString s;
        s=objForecast.value("high").toString().split(" ").at(1);    //18℃
        s=s.left(s.length()-1);       //18
        //s=s.left(2);
        mDay[i+1].high=s.toInt();

        s=objForecast.value("low").toString().split(" ").at(1);    //18℃
        s=s.left(s.length()-1);       //18
        //s=s.left(2);
        mDay[i+1].low=s.toInt();

        //风向风力
        mDay[i+1].fx=objForecast.value("fx").toString();
        mDay[i+1].fl=objForecast.value("fl").toString();

        //污染指数 aqi
        mDay[i+1].aqi=objForecast.value("aqi").toDouble();

    }

    //4.解析今天的数据 用mToday去存储
    mToday.ganmao=objData.value("ganmao").toString();

    mToday.wendu=objData.value("wendu").toString().toInt();
    mToday.shidu=objData.value("shidu").toString();
    mToday.pm25=objData.value("pm25").toDouble();
    mToday.quality=objData.value("quality").toString();

    //5.forcast中第一个数组元素,也是今天的数据
    //今天的数据也属于五天之中的数据,所以要把今天的数据mToday也给到mDay
    mToday.type = mDay[1].type;

    mToday.fx = mDay[1].fx;
    mToday.fl = mDay[1].fl;

    mToday.high = mDay[1].high;
    mToday.low = mDay[1].low;


    //6.更新UI
    updateUI();

    //6.2
    //绘制高温曲线
    ui->lblHighCurve->update();     //刷新
    ui->lblLowCurve->update();
}

void MainWindow::updateUI()
{
    //1.更新日期,右上角
    //ui->lblDate->setText(mToday.date)         //直接获取json的数据 得到的日期是20220210的格式,做一个转换
    ui->lblDate->setText(QDateTime::fromString(mToday.date,"yyyyMMdd").toString("yyyy/MM/dd")+" "+mDay[1].week);    //显示右上角的日期+星期

    //2.更新城市,右边 城市框
    ui->lblCity->setText(mToday.city);

    //3.更新今天数据  左侧今天的数据
    ui->lblTemp->setText(QString::number(mToday.wendu)+"°");            //温度
    qDebug()<<"-----------------------------------------";
    //qDebug()<<"今天温度:"<<mToday.wendu;

    ui->lblTypeIcon->setPixmap(mTypeMap[mToday.type]);              //天气图标,以通过type取到key,进到mTypeMap去获得它的value 继而获得图片资源地址,在贴上去,进而更新图标
    ui->lblType->setText(mToday.type);                              //天气文本
    ui->lblLowHigh->setText(QString::number(mToday.low)+"°"+"~"+QString::number(mToday.high)+"°");     //最低温到最高温
    //qDebug()<<"最低温度:"<<mToday.low << "最高温度:" <<mToday.high;

    ui->lblGanMao->setText("感冒指数: "+mToday.ganmao);               //感冒指数
    ui->lblWindFx->setText(mToday.fx);                              //风向
    ui->lblWindFl->setText(mToday.fl);                              //风力

    ui->lblPM25->setText(QString::number(mToday.pm25));             //PM2.5
    qDebug()<<"pm2.5:"<<mToday.pm25;

    ui->lblShiDu->setText(mToday.shidu);                            //湿度
    ui->lblQuality->setText(mToday.quality);                        //空气质量

    //4.更新六天数据,右侧界面
    for(int i=0;i<6;i++)
    {
        //4.1更新日期和时间
        mWeekList[i]->setText("周"+mDay[i].week.right(1));           //转换格式将星期几 转成周几
        ui->lblWeek0->setText("昨天");                                //将前三天固定写为昨天今天明天
        ui->lblWeek1->setText("今天");
        ui->lblWeek2->setText("明天");

        QStringList ymdList=mDay[i].date.split("-");                //json数据日期格式为:2023-02-18    以"-"为分割依据,将其分为3部分012
        mDateList[i]->setText(ymdList[1]+"/"+ymdList[2]);           //取ymdList的12部分,即02/18

        //4.2更新天气类型
        mTypeList[i]->setText(mDay[i].type);
        mTypeIconList[i]->setPixmap(mTypeMap[mDay[i].type]);        //取到type作为key,到mTypeMap中找到value

        //4.3更新空气质量
        //对aqi进行判断,赋予相应等级
        if(mDay[i].aqi >= 0 && mDay[i].aqi <= 50)
        {
            mAqiList[i]->setText("优");                              //0~50  优
            mAqiList[i]->setStyleSheet("background-color: rgb(121, 184, 0);");      //设置风格样式,改变颜色
        }
        else if(mDay[i].aqi >= 50 && mDay[i].aqi <= 100)
        {
            mAqiList[i]->setText("良");                              //50~100  良
            mAqiList[i]->setStyleSheet("background-color: rgb(255, 187, 23);");      //设置风格样式,改变颜色
        }
        else if(mDay[i].aqi >= 100 && mDay[i].aqi <= 150)
        {
            mAqiList[i]->setText("轻度");                              //100~150  轻度
            mAqiList[i]->setStyleSheet("background-color: rgb(255, 87, 97);");      //设置风格样式,改变颜色
        }
        else if(mDay[i].aqi >= 150 && mDay[i].aqi <= 200)
        {
            mAqiList[i]->setText("中度");                              //150~200  中度
            mAqiList[i]->setStyleSheet("background-color: rgb(235, 17, 27);");      //设置风格样式,改变颜色
        }
        else if(mDay[i].aqi >= 200 && mDay[i].aqi <= 250)
        {
            mAqiList[i]->setText("重度");                              //200~250  重度
            mAqiList[i]->setStyleSheet("background-color: rgb(170, 0, 0);");      //设置风格样式,改变颜色
        }
        else
        {
            mAqiList[i]->setText("严重");                              //大于250  严重
            mAqiList[i]->setStyleSheet("background-color: rgb(110, 0, 0);");      //设置风格样式,改变颜色
        }

        //4.4 更新风力风向
        mFxList[i]->setText(mDay[i].fx);
        mFlList[i]->setText(mDay[i].fl);



    }
}

bool MainWindow::eventFilter(QObject *watched, QEvent *event)       //事件过滤器,捕获事件
{
    //对绘制高低温曲线的控件进行监听,捕获其事件,以绘制曲线
    if(watched==ui->lblHighCurve && event->type()==QEvent::Paint){
        paintHighCurve();
    }
    if(watched==ui->lblLowCurve && event->type()==QEvent::Paint){
        paintLowCurve();
    }
    return QWidget::eventFilter(watched,event);
}

void MainWindow::paintHighCurve()
{
    QPainter painter(ui->lblHighCurve);     //绘画区域

    //抗锯齿
    painter.setRenderHint(QPainter::Antialiasing,true);     //抗锯齿效果,true是开关

    //1.获取x坐标
    int pointX[6] = {0};        //定义x坐标的数组
    for(int i=0;i<6;i++){
        pointX[i]=mWeekList[i]->pos().x()+mWeekList[i]->width()/2;  //x坐标=上方日期控件的中心位置=日期控件的坐标(左上角顶点)+控件宽度的一半,就是中心位置
    }

    //2.获取y坐标
    int tempSum = 0;
    int tempAverage=0;
    for(int i=0;i<6;i++){
        tempSum += mDay[i].high;        //对6天的高温进行求和
    }
    tempAverage=tempSum/6;          //最高温的平均值
    //图中显示的曲线,在lbael框中,中心位置应该为平均值,若高于平均值在其上方,低于在下方;高一度就向上移动3像素
        //计算y轴坐标
    int pointY[6]={0};              //定义y坐标的数组
    int yCenter =ui->lblHighCurve->height()/2;      //确定平均值所在的区域,为label区域高度一半的位置
    for(int i=0;i<6;i++){
        //Qt中 坐标计算,向下是+,向上是-
        pointY[i] =yCenter-((mDay[i].high-tempAverage) * INCREMENT);        //y坐标=中心值高度-(温度-平均值)*3像素      如果平均值16,当日温度17则y=h-(17-16)*3=h-3,即在中心位置向上移动3像素
    }

    //3.开始绘制
    //3.1初始化画笔相关工具
    QPen pen=painter.pen();                 //获取画笔
    pen.setWidth(1);                        //设置画笔的宽度
    pen.setColor(QColor(255,170,0));        //设置画笔的颜色

    painter.setPen(pen);                    //设置画笔
    painter.setBrush(QColor(255,170,0));    //设置画刷颜色,填充内部

    //3.2画点,文本
    for(int i=0;i<6;i++){
        //显示点
        painter.drawEllipse(QPoint(pointX[i],pointY[i]),POINT_RADIUS,POINT_RADIUS);         //绘制点(圆心坐标,xy半径(这里是xy的偏移)),但此时是空心圆,需要画刷填充

        //显示温度
        painter.drawText(pointX[i]-TEXT_OFFSET_X,pointY[i]-TEXT_OFFSET_Y,QString::number(mDay[i].high)+"°");        //绘制文本,使其位于点的上方,(绘制点坐标(要一定偏移,不然和点重合了),文本信息)
    }
    //3.3 连线    两点之间确定一条直线,一共绘制五次
    for(int i=0;i<5;i++){       //不能到6,因为素组是0-5的,如果i可以到5的话,pointX[i+1]就到6了,会越界
        if(i==0){
            pen.setStyle(Qt::DotLine);      //虚线
            painter.setPen(pen);
        }
        else{
            pen.setStyle(Qt::SolidLine);    //实线
            painter.setPen(pen);
        }
        painter.drawLine(pointX[i],pointY[i],pointX[i+1],pointY[i+1]);              //(x1,y1,x2,y2)
    }
}

void MainWindow::paintLowCurve()            //绘制低温曲线
{
    QPainter painter(ui->lblLowCurve);

    //抗锯齿
    painter.setRenderHint(QPainter::Antialiasing,true);

    //1.获取x坐标
    int pointX[6] = {0};
    for(int i=0;i<6;i++){
        pointX[i]=mWeekList[i]->pos().x()+mWeekList[i]->width()/2;
    }

    //2.获取y坐标
    int tempSum = 0;
    int tempAverage=0;
    for(int i=0;i<6;i++){
        tempSum += mDay[i].low;
    }
    tempAverage=tempSum/6;          //最高温的平均值

    //计算y轴坐标
    int pointY[6]={0};
    int yCenter =ui->lblLowCurve->height()/2;
    for(int i=0;i<6;i++){
        pointY[i] =yCenter-((mDay[i].low-tempAverage) * INCREMENT);
    }

    //3.开始绘制
    //3.1初始化画笔相关工具
    QPen pen=painter.pen();
    pen.setWidth(1);                        //设置画笔的宽度
    pen.setColor(QColor(0,255,255));        //设置画笔的颜色

    painter.setPen(pen);
    painter.setBrush(QColor(0,255,255));    //设置画刷颜色,填充内部

    //3.2画点,文本
    for(int i=0;i<6;i++){
        //显示点
        painter.drawEllipse(QPoint(pointX[i],pointY[i]),POINT_RADIUS,POINT_RADIUS);         //绘制点,但此时是空心圆,需要画刷填充

        //显示温度
        painter.drawText(pointX[i]-TEXT_OFFSET_X,pointY[i]-TEXT_OFFSET_Y,QString::number(mDay[i].low)+"°");        //绘制文本,使其位于点的上方,利用偏移
    }
    //3.3 连线
    for(int i=0;i<5;i++){       //不能到6,因为素组是0-5的,如果i可以到5的话,pointX[i+1]就到6了,会越界
        if(i==0){
            pen.setStyle(Qt::DotLine);      //虚线
            painter.setPen(pen);
        }
        else{
            pen.setStyle(Qt::SolidLine);    //实线
            painter.setPen(pen);
        }
        painter.drawLine(pointX[i],pointY[i],pointX[i+1],pointY[i+1]);
    }
}

void MainWindow::onReplied(QNetworkReply *reply)        //数据处理
{
    qDebug()<<"onReplied success";

    //响应状态码为200,表示请求成功
    int status_code=reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();

    qDebug()<<"operation: "<<reply->operation();        //请求方式
    qDebug()<<"status code: "<<status_code;             //状态码
    qDebug()<<"url: "<<reply->url();                    //url
    qDebug()<<"raw header"<<reply->rawHeaderList();     //header

    //如果指定的城市编码不存在,就会报错
    //"Error transfering  http://t.weather.itboy.net/api/weather/city/000000000 -server replied: Not Found"
    if(reply->error()!= QNetworkReply::NoError || status_code !=200){
        qDebug("%s(%d) error:%s",__FUNCTION__,__LINE__,reply->errorString().toLatin1().data());
        QMessageBox::warning(this,"天气","请求数据失败!",QMessageBox::Ok);
    }
    else{
        //获取响应信息
        QByteArray byteArray=reply->readAll();
        qDebug()<<"readAll:"<<byteArray.data();
        parseJson(byteArray);                       //调用parseJson解析数据
    }

    reply->deleteLater();

}


void MainWindow::on_btnSearch_clicked()
{
    QString cityName =ui->leCity->text();
    getWeatherInfo(cityName);
}


//系统托盘
void MainWindow::systemtrayiconActivated(QSystemTrayIcon::ActivationReason reason)      //响应系统托盘的动作(双击操作)
{
    switch(reason){
    case QSystemTrayIcon::DoubleClick:      //双击
        //显示  隐藏  界面
        if(isHidden())
        {
            show();
        }
        else
        {
            hide();
        }
        break;
    default:
        break;
    }
}

void MainWindow::initsystemtrayIcon()       //系统托盘初始化操作
{
    mysystemTray =new QSystemTrayIcon(this);
    mysystemTray->setIcon(QIcon(":/res/WeaFor.png"));       //设置图标
    connect(mysystemTray,&QSystemTrayIcon::activated,this,&MainWindow::systemtrayiconActivated);

    //添加应该退出应用程序菜单
    QAction  *actionsystemquit =new QAction(QIcon(":/res/close.png"),u8"退出程序");     //创建一个功能--退出程序以及它的图标
    connect(actionsystemquit,&QAction::triggered,this,[=](){
        qApp->exit(0);
    });

    QMenu *pcontextmenu=new QMenu(this);        //创建菜单项
    pcontextmenu->addAction(actionsystemquit);      //给这个菜单项增加退出程序的功能
    mysystemTray->setContextMenu(pcontextmenu);     //把这个菜单项加入系统托盘
    mysystemTray->show();

}
4.9.3 weatherdata.h
#ifndef WEATHERDATA_H
#define WEATHERDATA_H
#include<QString>

class Today{        //保存今天数据
public:
    Today()
    {
        date = "2022-10-20";
        city = "广州";

        ganmao = "感冒指数";

        wendu = 0;
        shidu= "0%";
        pm25 = 0;
        quality = "无数据";

        type= "多云";

        fl = "2级";
        fx = "南风";

        high = 30;
        low =18;
    }

    QString date;
    QString city;

    QString ganmao;

    int wendu;
    QString shidu;
    int pm25;
    QString quality;

    QString type;

    QString fx;
    QString fl;

    int high;
    int low;
};

class Day{      //保存六天数据
public:
    Day()
    {
        date = "2022-10-20";
        week = "周五";

        type = "多云";

        high = 0;
        low = 0;

        fx = "南风";
        fl = "2级";

        aqi =0 ;
    }
    QString date;
    QString week;

    QString type;

    int high;
    int low;

    QString fx;
    QString fl;

    int aqi;
};
#endif // WEATHERDATA_H

4.9.4 weathertool.h
#ifndef WEATHERTOOL_H
#define WEATHERTOOL_H
#include<QMap>
#include<QFile>

#include<QJsonArray>
#include<QJsonDocument>
#include<QJsonObject>
#include<QJsonParseError>
#include<QJsonValue>

class WeatherTool{

private:
    static QMap<QString,QString> mCityMap;      //声明一个静态成员,QMap

    static void initCityMap(){                  //初始化CityMap
        //1.读取文件
        QString filePath ="E:\\Project\\QT_Weather\\Qt_Weather\\citycode.json";

        QFile file(filePath);
        file.open(QIODevice::ReadOnly | QIODevice::Text);
        QByteArray json=file.readAll();
        file.close();

        //2.解析并写入map
        QJsonParseError err;
        QJsonDocument doc =QJsonDocument::fromJson(json,&err);         //如果解析错误,把错误信息存到err中
        if(err.error!= QJsonParseError::NoError)
        {
            return;
        }
        if(!doc.isArray())      //如果不是数组
        {
            return;
        }

        //开始解析
        QJsonArray cities=doc.array();      //把doc转换成QJsonArray格式
        for(int i=0;i<cities.size();i++)
        {
            QString city=cities[i].toObject().value("city_name").toString();
            QString code=cities[i].toObject().value("city_code").toString();

            //把获取到的json数据转换成QString格式然后存入QMap中
            if(code.size() > 0){                    //citycode.json文件当中,省份的city_code是空,不将其存入map
                mCityMap.insert(city,code);
            }
        }
    }

public:
    static QString getCityCode(QString cityName){       //声明静态成员函数getCityCode,通过key:city_name值去获得value:city_code
        if(mCityMap.isEmpty()){                         //如果位空就初始化citymap
            initCityMap();
        }
        QMap<QString,QString>::iterator it =mCityMap.find(cityName);        //获取iterator,进而拿到key和value
        //输入北京/北京市
        if(it==mCityMap.end()){
            it =mCityMap.find(cityName + "市");          //在遍历寻找北京市
        }
        if(it!=mCityMap.end()){
            return it.value();
        }
        return "";
    }
};

QMap<QString,QString> WeatherTool::mCityMap={};

#endif // WEATHERTOOL_H

5.项目打包

5.1.将编译模式设置为release

重新编译 运行程序

5.2. 拷贝一条文件路径

在release编译文件下的release文件夹中,将Qt_Weather.exe拷贝到桌面文件夹Weather中

 

5.3.运行windeployqt.exe,获取qt下的dll文件

打开一个命令行窗口,进入到Weather,查看当前目录下有一个Qt_Weather.exe

 

在QT的bin目录下,有一个windeployqt.exe的执行文件,就是用来在windows环境下打包发布qt程序

 

在命令行窗口中将windeployqt.exe文件执行

 E:\QT\5.15.2\mingw81_32\bin\windeployqt.exe .\Qt_Weather.exe        //执行windeployqt.exe,并给他一个值.\Qt_Weather.exe 

之后就会将CoinFlip.exe要用到的和qt相关的dll拷贝到当前release目录中,但是不是与qt相关的dll不会拷贝,需要手动添加

此时看Weather目录 多了很多dll文件

 

此时直接运行程序也是可以运行的,因为系统会去环境变量中找寻所需要的dll文件地址

release目录下多了很多文件,但可能仍然缺少几个文件

这时就得将系统环境变量下的Qt环境先删除,然后运行程序看看缺少哪些文件,然后手动添加过来

5.4 将系统中,qt相关的环境变量删除

 

然后运行Qt_Weather.exe程序,然后会报错缺少dll文件,缺少什么文件,就到QT的bin目录中找到并复制过去,直至可以正常运行

 

最终添加了三个dll文件 即可运行程序了

 

此时就算没有系统环境,这个程序也可以直接运行起来了,之后就是打包压缩成一个程序,就可以发布了

记得恢复系统环境变量,避免其他程序不能正常运行

5.5使用Enigma Virtual Box进行打包操作

 

 

浏览程序所在文件夹

 

接着添加一个文件夹递归

 

选择当前桌面的放置.exe文件的文件夹弹出对话框点击确定

 

接着点击文件选项,勾选压缩文件,点击确定

 

最后点击打包

 

等待结束, 结束后点击关闭,整个软件点击关闭,选择no,

最后返回文件夹, 就可以看到刚刚生成的Qt_Weather_boxed.exe程序

这个程序不需要dll执行文件也可以直接运行

GitHub:

​​​​​​​项目源码 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值