赞
踩
在右上角输入要查询的城市,然后点击查询
按钮,就会发送 Http 请求
给服务器
,请求回来的天气数据为 JSON 格式
通过解析 JSON
可以获取城市天气信息,并且更新到UI上
具体功能:
发送HTTP请求(GET请求),从国家气象局提供的API接口中获取到JSON格式的天气数据;
使用QT提供的解析JSON数据的QJson类来解析数据,将数据更新到项目界面上;
可查看昨天、今天以及未来4天的详细天气数据,空气质量、风向风力、湿度、感冒指数;
根据每天高低温度绘制温度曲线,对绘图控件安装事件过滤器,重写控件事件;
实现城市模糊搜索和窗口跟随鼠标移动功能;
效果演示:
技术点:界面的设置合理,HTTP请求,JSON数据,事件,绘图,资源文件
项目开始从第7步项目 开始,前面6点为运用到的知识的讲解
项目源文件及资源项目
问题:客户端和服务端之间通信采用什么数据格式合适,
比如 C++ 写的服务端,创建了一个 Person 对象, 怎么将服务端创建的 Person 对象传递到客户端?
直接传 person 对象肯定是不合适的,因为客户端可能甚至不是 C++ 写的,能是 Java 写的,Java 不认识 C++ 中的对象更有甚者, 客户端是一个单片机的设备,是用 语言写的, 语言是面向过程的,压根就没有类和对象的概念
方法:需要一种通用的数据格式,JSON
(是网络传输使用效率最高的数据格式)
通常说的 JSON
,其实就是 JSON
字符串,本质上是一种特殊格式的字符串
JSON
是一种轻量级的数据交换格式,负责不同编程语言中的数据传递和交互,客户端和服务端数据交互,基本都是 JSON 格式的
JSON
: JavaScript Object Notation(JavaScript 对象表示法)
JSON
是存储和交换文本信息的语法,类似 XML。
JSON
比 XML 更小、更快,更易解析。
JSON
易于人阅读和编写。
JSON 是轻量级的文本数据交换格式,网络传输的标准数据格式
JSON 独立于语言:JSON 使用 Javascript
语法来描述数据对象,但是 JSON 仍然独立于语言和平台。
一些JSON库:
#c
Jansson、cJSON
#c++
jsonCpp、JSON for Modern C++
#Java
json-ib、org-json
#QT
QJsonxxx
JSON 数组
[ "Google", "Runoob", "Taobao" ]
中括号中书写,值之间使用逗号分隔
JSON
数组中的元素可以是不同的数据类型,包括: 整形、 浮点、 字符串、布尔类型、JSON数组
、JSON 对象
、空值
JSON 对象
{ "name":"runoob", "alexa":10000, "site":null }
在大括号 {…} 中书写,key/value(键/值)对,key 和 value 中使用冒号分割,值之间使用逗号分隔
key 必须是字符串,value 是合法的 JSON 数据(字符串, 数字, 对象, 数组, 布尔值或 null),还可以嵌套JSON数组
、JSON 对象
QT编译器需要在.pro文件中 QT += Core
操作Json文件所需要用到的类:
编写json文件,解析Json文件
#include <QJsonArray> #include <QJsonObject> #include <QJsonDocument> #include <QFile> #include <QDebug> // "name":"china", // "info":{ // "capital":"beijing", // "asian": true , // "founded":1949 // }, // "provinces":[{ // "name":"shandong", // "capita1":"jinan" // },{ // "name","zhejiang", // "capital","hangzhou" // }] //写json字符串 void writeJson() { //1.把数据写入 //1.创建JSON对象 QJsonObject rootObj; //1.1插入name信息 rootObj.insert("name","China"); //1.2插入info信息,value是JSON对象 QJsonObject infoObj; infoObj.insert("capital","Beijing"); infoObj.insert("asion",true); infoObj.insert("founded",1949); rootObj.insert("info",infoObj); //1.3插入province信息,value为JSON数组 QJsonArray provinceArray; QJsonObject province1; QJsonObject province2; province1.insert("name","Shandong"); province1.insert("capital","Jinnan"); province2.insert("name","zhejiang"); province2.insert("capital","hangzhou"); provinceArray.append(province1); provinceArray.append(province2); rootObj.insert("provinces",provinceArray); //2.装换成JSON字符串 QJsonDocument doc(rootObj); QByteArray json = doc.toJson(); //3.测试 // qDebug() <<QString(json).toUtf8().data(); //4.写入到文件里面 QFile file("C:/Users/hanghang/Desktop/jsontest.txt"); file.open(QFile::WriteOnly); file.write(json); file.close(); } //解析json文件 void findJson() { //1.读取文件 QFile file("C:/Users/hanghang/Desktop/jsontest.txt"); file.open(QFile::ReadOnly); QByteArray json = file.readAll(); file.close(); //2.json转换成字符串,判断打开是否json QJsonDocument doc = QJsonDocument::fromJson(json); if(!doc.isObject()) { qDebug() <<" error, 不是一个jsonObject! "; return; } //3.解析 QJsonObject rootObj = doc.object(); //创建接受对象 QStringList keys = rootObj.keys(); for(int i = 0 ; i < keys.size() ; i++) { //获取每个key - value QString key = keys[i]; //jsonObject的Key值是String类型 //根据key来获取对应的value QJsonValue value = rootObj.value(key); if(value.isBool()) { qDebug() << key << ":" << value.toBool(); } else if (value.isDouble()) { qDebug() << key << ":" << value.toDouble(); } else if (value.isString()) { qDebug() << key << ":" << value.toString(); } else if (value.isObject()) { qDebug() << key << ":" ; //value是对象的话,也是先创建一个对象来接收它,再依次获取每个key - value //步骤一样,这里就直接默认当是知道key - value,直接写出来就算了 QJsonObject infoObj = value.toObject(); QString capital = infoObj.value("capital").toString(); bool asian = infoObj.value("asian").toBool(); int founded = infoObj.value("founded").toInt(); qDebug() << " " << "capital" << ":" << capital; qDebug() << " " << "asian" << ":" << !asian; qDebug() << " " << "founded" << ":" << founded; } else if (value.isArray()) { qDebug() << key << ":" ; //先创建一个数组来接收json数组 QJsonArray provinceArray = value.toArray(); for(int i = 0; i < provinceArray.size() ; i++) { QJsonObject provinceObj = provinceArray[i].toObject(); QString name = provinceObj.value("name").toString(); QString capital = provinceObj.value("capital").toString(); qDebug() << " " << "name" << ":" << name << " , " << "capital" << ":" << capital; } } } } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // writeJson(); findJson(); return a.exec(); }
HTTP 协议基于客户端/服务端C/S架构模型,通过一个可靠的链接来交换信息,是一个无状态的请求/响应协议。
HTTP 协议:是客户端发起请求,服务器做出响应请求,必是先从客户端发起的,服务器端在没有接收到请求之前不会发送任何响应也就是说,服务端无法主动推送消息给客户端
(无状态:服务器不会记住发起请求者的信息
有状态:http协议上带有cookie
)
B/s 架构:Browser /Server, 浏览器/服务器架构
B:浏览器,比如 Firefox、B:Internet Explorer、Goog1e chrome .
S:服务器,比如 Apache、nginx等
C/s架构:Client / server,客户端/服务器架构
B/s架构相对于c/s架构,客户机上无需安装任何软件使用浏览器即可访问服务器
HTTP的请求报文由四部分组成(请求行+请求头部+空行+请求体)
请求行:
请求头:
服务端据此获取客户端的额外的信息
包含若干个属性,格式为“属性名:属性值”
Client-IP:提供了运行客户端的机器的IP地址 From:提供了客户端用户的E-mail地址 Host:给出了接收请求的服务器的主机名和端口号 Referer:提供了包含当前请求URI的文档的URL UA-Color:提供了与客户端显示器的显示颜色有关的信息 UA-CPU:给出了客户端CPU的类型或制造商 UA-OS:给出了运行在客户端机器上的操作系统名称及版本 User-Agent:将发起请求的应用程序名称告知服务器 Accept:告诉服务器能够发送哪些媒体类型 Accept-Charset:告诉服务器能够发送哪些字符集 Accept-Encoding:告诉服务器能够发送哪些编码方式 Accept-Language:告诉服务器能够发送哪些语言 TE:告诉服务器可以使用那些扩展传输编码 Expect:允许客户端列出某请求所要求的服务器行为 Range:如果服务器支持范围请求,就请求资源的指定范围 Cookie:客户端用它向服务器传送数据 Cookie2:用来说明请求端支持的cookie版本
空行:
请求体:
就是实际传输的数据信息
GET没有请求数据,POST有。
与请求数据相关的最常使用的请求头是 Content-Type 和 Content-Length
HTTP的响应报文也由四部分组成( 响应行+响应头+空行+响应体):
响应行:
响应头:
传递一些额外的信息,响应时间等
Age:(从最初创建开始)响应持续时间
Public:服务器为其资源支持的请求方法列表
Retry-After:如果资源不可用的话,在此日期或时间重试
Server:服务器应用程序软件的名称和版本
Title:对HTML文档来说,就是HTML文档的源端给出的标题
Warning:比原因短语更详细一些的警告报文
Accept-Ranges:对此资源来说,服务器可接受的范围类型
Vary:服务器会根据这些首部的内容挑选出最适合的资源版本发送给客户端
Proxy-Authenticate:来自代理的对客户端的质询列表
Set-Cookie:在客户端设置数据,以便服务器对客户端进行标识
Set-Cookie2:与Set-Cookie类似
WWW-Authenticate:来自服务器的对客户端的质询列表
空行:
响应体:
实际数据
当Web服务器接收到Web客户端的请求报文后,对HTTP请求报文进行解析,并将Web客户端的请求的对象取出打包,通过HTTP响应报文将数据传回给Web客户端,如果出现错误则返回包含对应错误的错误代码和错误原因的HTTP响应报文
Postman 是一个接口测试工具,用来模拟各种HTTP 请求的 (比如 GET 请求POST 请求等)
在做接口测试的时候,Postman 相当于客户端,它可模拟用户发起的各类HTTP 请求,将请求数据发送至服务端,并获取对应的响应结果
示例:
北京天气:http://t.weather.itboy.net/api/weather/city/101010100
101010100是北京的地址编号
.
6.1步骤
mNetAccessManager = new QNetworkAccessManager(this);
connect(mNetAccessManager, &0NetworkAccessManager::finished, this , &Mainwindow: :onReplied);
当请求结束,获取到服务器的数据时,mNetAccessManager
会发射一个finished
信号,该信号携带一个 NetworkReply
的参数服务器返回的所有数据就封装在其中,通过QNetworkRep1y
类提供的各种方法,就可以获取响应头,响应体等各种数据
QUrl url("http://t.weather.itboy.net/api/weather/city/101010100");
mNetAccessManager->get(QNetworkRequest(ur1));
根据请求的地址构建出一个 Qurl
对象,然后直接调用 QNetworkAccessManager
的 get
方法,即可发送一个GET
请求
接收数据,调用我们自定义的槽函数 onReplied
void 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: " << reply->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(); } reply->deleteLater(); } //QNetworkRep1y 中封装了服务器返回的所有数据,包括响应头、状态码、响应体等
项目成品及图标、城市编码资源文件:https://pan.baidu.com/s/1TiqlngwBKhqlnp557nrRTA?pwd=6666
提取码:6666
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//设置界面无边框
setWindowFlag(Qt::FramelessWindowHint);
//固定界面大小
setFixedSize(800,450);
}
MainWindow.h
#include <QContextMenuEvent> class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); protected: void contextMenuEvent(QContextMenuEvent *event) override; //鼠标右键点击事件 private: Ui::MainWindow *ui; QMenu* mExitMenu; //右键退出的菜单 QAction* mExitAction; //退出的行为-菜单项 };
MainWindow.cpp
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); ... //构建右键退出菜单 mExitMenu = new QMenu(this); mExitAction = new QAction(); mExitAction->setText("退出"); mExitAction->setIcon(QIcon(":/weatherImages/close.png")); mExitMenu->addAction(mExitAction); //关联退出信号 connect(mExitAction,&QAction::triggered,this,[=](){ qApp->exit(0); }); } //重写父类的虚函数,父类中默认的实现 是忽略右键菜单事件,重写之后,就可以处理右键菜单 //鼠标右键点击事件重写 void MainWindow::contextMenuEvent(QContextMenuEvent *event) { //右键弹出退出选项,QCursor::pos()在鼠标当前位置显示一个弹出菜单 mExitMenu->exec(QCursor::pos()); }
样式表如下:
QWidget#widget
{
border-image:url(:/weatherImages/background.png);
}
QLabel {
font: 25 10pt "微软雅黑";
border-radius: 4px;
background-color: rgba(60, 60, 60, 100);
color: rgb(255, 255, 255);
}
效果如图:
最终效果如图:
background-color: rgba(0, 170, 255,0);
cityLineEdit (15,10)200*28
font: 14pt "Microsoft YaHei UI";
background-color: rgb(255, 255, 255);
border-radius: 4px;
padding: 1px 5px
background-color: rgba(157, 133, 255,0);
font: 20pt "Arial";
background-color: rgba(255, 255, 255,0);
最终效果:
widget_3
background-color: rgba(157, 133, 255, 0);
border-radius: 15px
lblTypeIcon (11,11)110*110
background-color: rgba(255, 255, 255, 0);
.
先把这4个lable准备好,写好对应的文字和简明知意的label名字
样式表如下
lblTemperture
color: rgb(255, 255, 255);
font: 50pt "Arial";
background-color: rgba(0, 255, 255, 0);
lblCity
font: 12pt "Microsoft YaHei UI";
background-color: rgba(255, 255, 255,0);
lblTypeChange
background-color: rgba(255, 255, 255,0);
font: 12pt "微软雅黑";
lblLowHight
background-color: rgba(255, 255, 255,0);
font: 12pt "微软雅黑";
把lblCity放入到一个垂直布局QVboxLayout
当中
QHboxLayout
当中,再增加一个line
来分割一下(没有也行),弹簧修饰把lblTemperture、lblCity放到一个水平布局当中,加个弹簧
然后把这两个水平布局拉到一个垂直布局里面,这里要慢慢拉,有点难拉
最终效果图:红色部分
lblGanmao (10,18) 320*70
background-color: rgba(60, 60, 60, 0);
padding-left: 5px;
padding-right: 5px;
//感冒指数:儿童、老年人及心脏、呼吸系统疾病患者人群应减少长时间或高强度户外锻炼
QWidget {
background-color: rgb(157, 133, 255);
border-radius: 15px;
}
QLabel {
font: 10pt "微软雅黑";
background-color: rgba(255, 255, 255, 0);
}
lblWindIcon 40*40
lblWindFx 50*20 text = “西北风”
lblWindFL 30*20 text =“ 3级”
把lblWindFL 、lblWindFx 放到一个垂直布局里面
然后复制四份,改一下标签名和对应名字即可
最后放到刚才紫色的widget里面,再进行栅格布局就行
最终效果图:
//第一个widget样式表
QLabel {
background-color: rgba(0, 200, 200, 200);
border-radius: 4px;
}
//第二个widget样式表
QLabel {
background-color: rgba(60, 60, 60, 100);
border-radius: 4px;
}
第二个widget,添加12个lable,采用栅格布局,lable把图片引进即可,pixmap
第三个widget, 6个lable,水平布局
//6个lable从上到下的样式表
background-color: rgb(121, 184, 0);
padding:8px;
background-color: rgb(255, 187, 23);
background-color: rgb(255, 87, 97);
background-color: rgb(235, 17, 27);
background-color: rgb(170, 0, 0);
background-color: rgb(110, 0, 0);
第四个widget,两个lable ,留作温度曲线的展示,垂直布局
第五个widget,12个lable,栅格布局
MainWindow.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
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);
}
QT += core gui network
添加用于http
通信的QNetworkAccessManager
对象,
用于处理http
服务返回的数据onReplied
#include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkReply> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); protected: void getWeatherInfo(QString cityCode); //获取对应城市的信息,用城市编码区分 private slots: void onReplied(QNetworkReply* reply); //处理http请求 private: QNetworkAccessManager * mNetAccessManager; };
关联信号槽,当getWeatherInfo
函数中mNetAccessManager
get
执行后 就会自动调用 槽函数onReplied
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { //关联请求服务器数据,HTTP请求 mNetAccessManager = new QNetworkAccessManager(this); //请求完成即finished之后,就会自动onReplied函数 connect(mNetAccessManager,&QNetworkAccessManager::finished,this,&MainWindow::onReplied); //直接获取北京天气数据信息 getWeatherInfo("101010100"); } //根据城市编码返回信息 void MainWindow::getWeatherInfo(QString cityCode) { QUrl url("http://t.weather.itboy.net/api/weather/city/" + cityCode); //这个网址返回的数据时json格式 mNetAccessManager->get(QNetworkRequest(url)); //get请求完成就会是finished,上面的槽函数onReplied就会被调用 } //请求服务器数据 void MainWindow::onReplied(QNetworkReply *reply) { qDebug() << "onReplied successfully!" ; //响应的信息 // 响应的状态码为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 if(reply->error() != QNetworkReply::NoError || status_code != 200) { //errorString()返回一个QString类型字符串,而toLatin1()将该QString转换为使用Latin-1编码QByteArray类型。 //最后,data()函数返回QByteArray的数据指针 qDebug() << reply->errorString().toLatin1().data(); QMessageBox::warning(this,"提示","请求数据失败!",QMessageBox::Ok); }else{ //获取响应信息 QByteArray reply_data = reply->readAll(); QByteArray byteArray = QString(reply_data).toUtf8(); qDebug() << "read All: " << byteArray.data(); } //不然会造成内存泄漏 reply->deleteLater(); }
weatherdata.h
由于界面上主要显示的是“今天"的天气,以及"最近六天"的天气,因此我们新建一个c++的头文件 weatherdata.h
,并定义两个类
Today
用于显示今天的所有天气参数,也就是屏幕左侧的数据
Day
用于显示六天的天气参数,也就是屏幕右侧的数据这样,可以方便地将解析出的数据保存到类的成员变量
//根据我们之前设计的UI布局的lable名称来获取设计对应的数据名称 class Today { public: Today() { city ="广州"; date ="2023-7-28"; wendu = 0; type ="多云"; high = 30; low = 18; ganmao ="感冒指数"; shidu = "0%"; pm25 = 0; fx ="南风"; fl ="2级"; quality ="无数据"; } QString city; QString date; int wendu; QString type; int high; int low; QString ganmao; QString shidu; int pm25; QString fx; QString fl; QString quality; }; class Day { public: Day() { week ="周五"; date ="2023-7-29"; type ="多云"; aqi = 0; //空气指数,优 high = 0; low = 0; fx ="南风"; fl ="2级"; } QString week; QString date; QString type; int aqi; int high; int low; QString fx; QString fl; };
mainwindow.h
#include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkReply> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT protected: void parseJson(QByteArray & byteArray); //解析json private: Today mToday; //json解析到到数据存到这两个类里面 Day mDay[6]; //6天的数据 };
在onReplied
函数中调用
//请求服务器数据
void MainWindow::onReplied(QNetworkReply *reply)
{
if(reply->error() != QNetworkReply::NoError || status_code != 200)
{
}else{
//获取响应信息
QByteArray reply_data = reply->readAll();
QByteArray byteArray = QString(reply_data).toUtf8();
qDebug() << "read All: " << byteArray.data();
//获取到json数据后进行解析
parseJson(byteArray);
}
}
解析过程根据返回来的json数据编写,用json解析工具解析数据,再一一对应的写
void MainWindow::parseJson(QByteArray &byteArray) { QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(byteArray,&error); //如果报错就会把错误信息写到error里 //报错直接退出 if(error.error != QJsonParseError::NoError) { return; } QJsonObject rootObj = doc.object(); // qDebug() << rootObj.value("message").toString(); //1.解析城市跟日期 mToday.city = rootObj.value("cityInfo").toObject().value("city").toString(); mToday.date = rootObj.value("date").toString(); //2.解析昨天yesterday数据 QJsonObject dataObj = rootObj.value("data").toObject(); QJsonObject yesterdayObj = dataObj.value("yesterday").toObject(); mDay[0].week = yesterdayObj.value("week").toString(); mDay[0].date = yesterdayObj.value("ymd").toString(); //天气类型 mDay[0].type = yesterdayObj.value("type").toString(); //空气指数 mDay[0].aqi =yesterdayObj.value("aqi").toDouble(); //温度,要分割取数据,"high":"高温 32℃" QString highS; highS = yesterdayObj.value("high").toString().split(" ").at(1); //高温 32℃ -> 32°C ,按空格切割,取第二个数据 mDay[0].high = highS.left(highS.length() - 1).toInt(); //长度减一,从左边开始计算取,32°c -> 32 ,在转换为int QString lowS; lowS = yesterdayObj.value("low").toString().split(" ").at(1); mDay[0].low = lowS.left(lowS.length() - 1).toInt(); //风向,风力 mDay[0].fx = yesterdayObj.value("fx").toString(); mDay[0].fl = yesterdayObj.value("fl").toString(); //3.解析forecast中5天的数据 ,forecast当中包括15天的数据,数组形式 QJsonArray forecastArr = dataObj.value("forecast").toArray(); for(int i=0 ; i<5 ; i++) { QJsonObject forecastObj = forecastArr[i].toObject(); mDay[i+1].week = forecastObj.value("week").toString(); mDay[i+1].date = forecastObj.value("ymd").toString(); mDay[i+1].type = forecastObj.value("type").toString(); //空气指数 mDay[0].aqi =forecastObj.value("aqi").toDouble(); //温度,要分割取数据,"high":"高温 32℃" QString highS; highS = forecastObj.value("high").toString().split(" ").at(1); mDay[i+1].high = highS.left(highS.length() - 1).toInt(); QString lowS; lowS = forecastObj.value("low").toString().split(" ").at(1); mDay[i+1].low = lowS.left(lowS.length() - 1).toInt(); //风向,风力 mDay[i+1].fx = forecastObj.value("fx").toString(); mDay[i+1].fl = forecastObj.value("fl").toString(); } //4.解析今天的数据 mToday.wendu =dataObj.value("wendu").toInt(); mToday.ganmao =dataObj.value("ganmao").toString(); mToday.shidu = dataObj.value("shidu").toString(); mToday.pm25 =dataObj.value("pm25").toInt(); mToday.quality =dataObj.value("quality").toString(); //还有一些今天的天气数据在forecast里面 mToday.type = mDay[1].type; //上面存好了,直接用 mToday.high = mDay[1].high; mToday.low = mDay[1].low; mToday.fx = mDay[1].fx; mToday.fl = mDay[1].fl; }
mainwindow.h
#include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkReply> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { private: //界面右边部分label名称,用数组存起来,用于ui更新 //星期和日期 QList<QLabel* > mWeekList; QList<QLabel* > mDateList; //天气和天气图标 QList<QLabel* > mTypeList; QList<QLabel* > mTypeIconList; //天气污染指数,优…… QList<QLabel* > mAqiList; //风向,风力 QList<QLabel* > mFxList; QList<QLabel* > mFlList; //图标 QMap<QString,QString> mTypeIconMap; };
mainwindow.cpp
将UI上的label写到数组里面
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { // 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; //图标 mTypeIconMap.insert("晴", ":/weatherImages/type/Qing.png"); mTypeIconMap.insert("多云", ":/weatherImages/type/DuoYun.png"); mTypeIconMap.insert("阴", ":/weatherImages/type/Yin.png"); mTypeIconMap.insert("雨", ":/weatherImages/type/Yu.png"); mTypeIconMap.insert("雪", ":/weatherImages/type/Xue.png"); mTypeIconMap.insert("沙尘暴", ":/weatherImages/type/ShaChenBao.png"); mTypeIconMap.insert("雷阵雨", ":/weatherImages/type/LeiZhenYu.png"); mTypeIconMap.insert("大雨", ":/weatherImages/type/DaYu.png"); mTypeIconMap.insert("小雨", ":/weatherImages/type/XiaoYu.png"); mTypeIconMap.insert("中雨", ":/weatherImages/type/ZhongYu.png"); mTypeIconMap.insert("阵雨", ":/weatherImages/type/ZhenYu.png"); mTypeIconMap.insert("暴雨", ":/weatherImages/type/BaoYu.png"); mTypeIconMap.insert("大暴雨", ":/weatherImages/type/DaBaoYu.png"); mTypeIconMap.insert("大到暴雨",":/weatherImages/type/DaDaoBaoYu.png"); mTypeIconMap.insert("暴雨到大暴雨",":/weatherImages/type/BaoYuDaoDaBaoYu.png"); mTypeIconMap.insert("大暴雨到大暴雨",":/weatherImages/type/DaBaoYuDaoDaBaoYu.png"); mTypeIconMap.insert("暴雪",":/weatherImages/type/BaoXue.png"); mTypeIconMap.insert("大到暴雪",":/weatherImages/type/DaDaoBaoXue.png"); mTypeIconMap.insert("大雪", ":/weatherImages/type/DaXue.png"); mTypeIconMap.insert("小雪", ":/weatherImages/type/XiaoXue.png"); mTypeIconMap.insert("中雪", ":/weatherImages/type/ZhongXue.png"); mTypeIconMap.insert("雨夹雪", ":/weatherImages/type/YuJiaXue.png"); mTypeIconMap.insert("霾", ":/weatherImages/type/Mai.png"); mTypeIconMap.insert("扬沙", ":/weatherImages/type/YangSha.png"); mTypeIconMap.insert("沙尘暴", ":/weatherImages/type/ShaChenBao.png"); mTypeIconMap.insert("特大暴雨", ":/weatherImages/type/TeDaBaoYu.png"); mTypeIconMap.insert("乌", ":/weatherImages/type/Wu.png"); mTypeIconMap.insert("小到中雨", ":/weatherImages/type/XiaoDaoZhongYu.png"); mTypeIconMap.insert("小到中雪", ":/weatherImages/type/XiaoDaoZhongXue.png"); mTypeIconMap.insert("雨夹雪", ":/weatherImages/type/YuJiaXue.png"); mTypeIconMap.insert("阵雪", ":/weatherImages/type/ZhenXue.png"); }
更新函数,将获取到的数据更新到UI
void MainWindow::updateUI() { //设置日期 城市 //注意返回来的值为“20230728”这种格式,要进行转换2023/07/28 ui->lblDate->setText(QDateTime::fromString(mToday.date,"yyyyMMdd").toString("yyyy/MM/dd") + " " + mDay[1].week); ui->lblCity->setText(mToday.city); //更新今天 ui->lblTypeIcon->setPixmap(mTypeIconMap[mToday.type]); ui->lblTemperture->setText(QString::number(mToday.wendu)); ui->lblTypeChange->setText(mToday.type); ui->lblLowHight->setText(QString::number(mToday.low) + "°" +"~" + QString::number(mToday.high) + "°"); ui->lblGanmao->setText("感冒指数:" + mToday.ganmao); ui->lblWinFx->setText(mToday.fx); ui->lblWinFI->setText(mToday.fl); ui->lblPM25->setText(QString::number(mToday.pm25)); ui->lblShiDu->setText(mToday.shidu); ui->lblQuality->setText(mToday.quality); //更新6天 for(int i=0 ; i<6 ; i++) { //更新星期 mWeekList[i]->setText("周" + mDay[i].week.right(1)); //数据是”星期六“,取右边第一位 ui->lblWeek0->setText("昨天"); ui->lblWeek1->setText("今天"); ui->lblWeek2->setText("明天"); //更新日期 ,数据是”2023-07-28“ QStringList ymdList = mDay[i].date.split("-"); mDateList[i]->setText(ymdList[1] + "/" + ymdList[2]); //更新天气类型 mTypeList[i]->setText(mDay[i].type); mTypeIconList[i]->setPixmap(mTypeIconMap[mDay[i].type]); //更新空气质量 if (mDay[i].aqi >= 0 && mDay[i].aqi <= 50) { mAqiList[i]->setText("优"); mAqiList[i]->setStyleSheet("background-color: rgb(121, 184, 0);"); } else if (mDay[i].aqi > 50 && mDay[i].aqi <= 100) { mAqiList[i]->setText("良"); mAqiList[i]->setStyleSheet("background-color: rgb(255, 187, 23);"); } else if (mDay[i].aqi > 100 && mDay[i].aqi <= 150) { mAqiList[i]->setText("轻度"); mAqiList[i]->setStyleSheet("background-color: rgb(255, 87, 97);"); } else if (mDay[i].aqi > 150 && mDay[i].aqi <= 200) { mAqiList[i]->setText("中度"); mAqiList[i]->setStyleSheet("background-color: rgb(235, 17, 27);"); } else if (mDay[i].aqi > 200 && mDay[i].aqi <= 250) { mAqiList[i]->setText("重度"); mAqiList[i]->setStyleSheet("background-color: rgb(170, 0, 0);"); } else { mAqiList[i]->setText("严重"); mAqiList[i]->setStyleSheet("background-color: rgb(110, 0, 0);"); } //更新风 mFxList[i]->setText(mDay[i].fx); mFlList[i]->setText(mDay[i].fl); } }
在解析完数据之后就可以更新UI
//解析数据
void MainWindow::parseJson(QByteArray &byteArray)
{
//更新UI
updateUI();
}
实现搜索城市,获取到相对应的天气数据,并且更新到UI上
//weatherTool的数据,懒得分开了,注意报错什么多重定义的,不要把头文件 #include "weathertool.h" 的引入放到这里
// 在头文件中定义了静态变量,然后在多个源文件中包含了这个头文件,
// 就会出现同一个变量被多次定义的情况,从而导致链接错误,直接放在头文件就行
mainwindow.h
#include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkReply> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { public: void initCityCode(); // 初始化城市数据,读取解析一个json文件,将数据存到map里面 QString getCityCode(QString cityName); //在map里面根据城市名字找对应编码 private: //城市编码 QMap<QString,QString> mCityMap; };
mainwindow.cpp
中getWeatherInfo()
函数中调用
void MainWindow::getWeatherInfo(QString cityName) //原来是getWeatherInfo(QString cityCode) { QString cityCode = getCityCode(cityName); if(cityCode.isEmpty()) { QMessageBox::warning(this,"错误","请检查是否输入正确的城市",QMessageBox::Ok); return; } QUrl url("http://t.weather.itboy.net/api/weather/city/" + cityCode); mNetAccessManager->get(QNetworkRequest(url)); //get请求完成就会是finished,上面的槽函数onReplied就会被调用 } // QString MainWindow::getCityCode(QString cityName) { // 初始化map if (mCityMap.isEmpty()) { initCityCode(); } // 根据城市名字遍历map,获取城市编码 QMap<QString, QString>::iterator it = mCityMap.find(cityName); // 特殊:输入北京/北京市都可以找到 if (it == mCityMap.end()) { it = mCityMap.find(cityName + "市"); } if (it == mCityMap.end()) { it = mCityMap.find(cityName + "区"); } if (it == mCityMap.end()) { it = mCityMap.find(cityName + "县"); } // 如果遍历到有这个城市名字,就返回编码 if (it != mCityMap.end()) { return it.value(); } return ""; // 没有就返回空 } //初始化 void MainWindow::initCityCode() { // 读取文件 QFile file(":/citycode.json"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { // 处理文件打开失败的情况 return; } QByteArray json = file.readAll(); file.close(); // 解析json,并且写入到map,这个文件是json数组 QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(json, &error); if (error.error != QJsonParseError::NoError || !doc.isArray()) { // 处理JSON解析失败的情况 return; } // 然后获取json数据当中的城市名字和对应的编码 QJsonArray cityArr = doc.array(); for (int i = 0; i < cityArr.size(); i++) { QString city = cityArr[i].toObject().value("city_name").toString(); QString code = cityArr[i].toObject().value("city_code").toString(); // 然后写入到map // 注意当输入是省份时,是没有城市编码的,不可以查整个省份的天气,只能是具体的城市 if (!code.isEmpty()) { mCityMap.insert(city, code); } } }
mainwindow.cpp
中关联搜索点击信号和输入回车信号
//搜索按钮点击
void MainWindow::on_btnSearch_clicked()
{
QString cityname = ui->Cityline->text();
getWeatherInfo(cityname);
}
//输入框回车
void MainWindow::on_Cityline_returnPressed()
{
QString cityname = ui->Cityline->text();
getWeatherInfo(cityname);
}
MainWindow.h
#include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkReply> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { protected: bool eventFilter(QObject *watched, QEvent *event) override; //重写绘图事件 //绘制曲线 void paintHight(); void paintLow(); };
mainwindow.cpp
注意这里事件过滤器的步骤
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
//温度曲线
//步骤1 安装事件过滤器
ui->lblHightCurve->installEventFilter(this);
ui->lblLowCurve->installEventFilter(this);
}
//注意这里要在更新UI函数这里调用Upadate函数,不然就会一直显示都是温度曲线0 //因为eventFilter会最先调用,请求服务器那些语句都还没执行,所以不会有数据 //在更新UI函数中,在调用一次eventFilter函数就会有数据,此时请求数据的那些函数已经被执行 //步骤2重写事件 bool MainWindow::eventFilter(QObject *watched, QEvent *event) { //处理绘图事件(QEvent::Paint)。 //在这个事件处理函数中,首先通过检查事件的类型(event->type() == QEvent::Paint)来确定是否是绘图事件 if(event->type() == QEvent::Paint) { //"watched" 是否等于 "ui->lblHight",来判断是否是标签控件 "ui->lblHight" 的绘图事件 if(watched == ui->lblHightCurve) { paintHight(); } if(watched == ui->lblLowCurve) { paintLow(); } } return QWidget::eventFilter(watched,event); } void MainWindow::paintHight() { //在lblHighCurve上绘图 QPainter painter(ui->lblHightCurve); //设置 QPainter::Antialiasing 标志来启用抗锯齿功能,产生更平滑的图形效果 painter.setRenderHint(QPainter::Antialiasing,true); //1.计算温度点的坐标(X,Y) //X int pointX[6] = {0}; for(int i = 0; i < 6 ;i++) { //6个点,平均分成5等分 //ui->lblHight->pos().x()用于获取lblHightCurve相对于其父级或包含的窗口的左边的x坐标,就是label的左边边边的位置 //为了更好看,设置一个边距DISTANCE 10 pointX[i] = mWeekList[i]->pos().x()+(mWeekList[i]->width()/2); } //Y int tempertureSum = 0; int tempertureAvg = 0; int yCenter = ui->lblHightCurve->height() / 2; //中心轴,平均温度绘制在这里 int moveDistance = ui->lblHightCurve->height() / 20 ; //偏移量,根据实际温度与平均温度的差值*偏移量 来计算距离中心轴的距离 for(int i = 0; i < 6 ;i++) { tempertureSum += mDay[i].high; } tempertureAvg = tempertureSum / 6; int pointY[6] = {0}; for(int i = 0; i < 6 ;i++) { //y轴方向为向下 pointY[i] = yCenter - (mDay[i].high - tempertureAvg)*moveDistance; } //2.设置画笔,字体 QPen pen = painter.pen(); pen.setWidth(2); pen.setColor(QColor(250,170,0)); painter.setPen(pen); painter.setBrush(QColor(250,170,0)); painter.setFont(QFont("Microsoft YaHei", 10)); //3.画温度点,设置文本 for(int i=0 ; i<6 ; i++) { painter.drawEllipse(QPoint(pointX[i],pointY[i]),2,2); painter.drawText(QPoint(pointX[i] - TEXT_OFFSET_X ,pointY[i] - TEXT_OFFSET_Y),QString::number(mDay[i].high) + "°"); } //4、连线 for(int i = 0 ; i < 5 ; i++) { //第一天是虚线 if(i==0) { pen.setStyle(Qt::DotLine); painter.setPen(pen); } else { pen.setStyle(Qt::SolidLine); painter.setPen(pen); } painter.drawLine(QPoint(pointX[i],pointY[i]),QPoint(pointX[i+1],pointY[i+1])); } } void MainWindow::paintLow() { //在lblHighCurve上绘图 QPainter painter(ui->lblLowCurve); //设置 QPainter::Antialiasing 标志来启用抗锯齿功能,产生更平滑的图形效果 painter.setRenderHint(QPainter::Antialiasing,true); //1.计算温度点的坐标(X,Y) //X int pointX[6] = {0}; for(int i = 0; i < 6 ;i++) { //6个点,平均分成5等分 //ui->lblHight->pos().x()用于获取lblHightCurve相对于其父级或包含的窗口的左边的x坐标,就是label的左边边边的位置 //为了更好看,设置一个边距DISTANCE 10 pointX[i] = mWeekList[i]->pos().x()+(mWeekList[i]->width()/2); } //Y int tempertureSum = 0; int tempertureAvg = 0; int yCenter = ui->lblLowCurve->height() / 2; //中心轴,平均温度绘制在这里 int moveDistance = ui->lblLowCurve->height() / 20 ; //偏移量,根据实际温度与平均温度的差值*偏移量 来计算距离中心轴的距离 for(int i = 0; i < 6 ;i++) { tempertureSum += mDay[i].low; } tempertureAvg = tempertureSum / 6; int pointY[6] = {0}; for(int i = 0; i < 6 ;i++) { //y轴方向为向下 pointY[i] = yCenter - (mDay[i].low - tempertureAvg)*moveDistance; } //2.设置画笔,字体 QPen pen = painter.pen(); pen.setWidth(2); pen.setColor(QColor(0,255,255)); painter.setPen(pen); painter.setBrush(QColor(0,255,255)); painter.setFont(QFont("Microsoft YaHei", 10)); //3.画温度点,设置文本 for(int i=0 ; i<6 ; i++) { painter.drawEllipse(QPoint(pointX[i],pointY[i]),2,2); painter.drawText(QPoint(pointX[i] - TEXT_OFFSET_X ,pointY[i] - TEXT_OFFSET_Y),QString::number(mDay[i].low) + "°"); } //4、连线 for(int i = 0 ; i < 5 ; i++) { //第一天是虚线 if(i==0) { pen.setStyle(Qt::DotLine); painter.setPen(pen); } else { pen.setStyle(Qt::SolidLine); painter.setPen(pen); } painter.drawLine(QPoint(pointX[i],pointY[i]),QPoint(pointX[i+1],pointY[i+1])); } }
//更新UI
void MainWindow::updateUI()
{
//更新温度曲线
ui->lblHightCurve->update();
ui->lblLowCurve->update();
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。