项目简介:
1,使用Qt设计一款天气预报的程序,主要包括:
- 界面显示:当日天气情况、空气质量等级、温湿度等空气指数、每日祝福语以及未来几日天气预报;根据近几日的温度数据绘制温度曲线;支持搜索指定城市功能以及刷新数据功能。
- 数据处理:QJson的读取与解析。
- 学习和使用Qt的信号与槽。
2,运行效果图(天气预报数据来源于网络):
部分代码整理:
1,温度曲线的绘制
void PaintCurve()
{
QPainter painter(ui->curveLb);
//反锯齿 使绘制出的线光滑
painter.setRenderHint(QPainter::Antialiasing,true);
int iTempTotal=0;
int high[6]={};
QString strHigh;
for(int i=0;i<6;++i)
{
strHigh=m_forecast[i].high;
strHigh=strHigh.mid(3);
strHigh=strHigh.left(2);
high[i]=(int)(strHigh.toDouble());
iTempTotal+=high[i];
}
//高温的平均值
int iTempAverage=(int)(iTempTotal/6);
//算出温度对应的点坐标
int pointX[6]={45,133,221,309,397,485};//x坐标的具体计算要结合所设置的控件宽度以及要显示数据的组数
int pointY_h[6]={};
for(int i=0;i<6;++i)
{
pointY_h[i]=TEMPERATURE_STARTING_COORDINATE - ((high[i]-iTempAverage)* SPAN_INDEX);
}
QPen pen=painter.pen();
pen.setWidth(1);//设置画笔的宽度
//高温曲线绘制
painter.save();
//昨天到今天的数据
pen.setColor(QColor(255,170,0));
pen.setStyle(Qt::DotLine);
painter.setPen(pen);
painter.setBrush(QColor(255,170,0));
painter.drawEllipse(QPoint(pointX[0],pointY_h[0]),ORIGIN_SIZE,ORIGIN_SIZE);
painter.drawEllipse(QPoint(pointX[1],pointY_h[1]),ORIGIN_SIZE,ORIGIN_SIZE);
painter.drawLine(pointX[0],pointY_h[0],pointX[1],pointY_h[1]);
//今天到未来四天的数据
pen.setStyle(Qt::SolidLine);
pen.setWidth(1);
painter.setPen(pen);
for(int i=1;i<5;++i)
{
painter.drawEllipse(QPoint(pointX[i+1],pointY_h[i+1]),ORIGIN_SIZE,ORIGIN_SIZE);
painter.drawLine(pointX[i],pointY_h[i],pointX[i+1],pointY_h[i+1]);
}
painter.restore();
}
其中m_forecast是封装的类Forecast的对象,主要保存天气的数据(或者说是个结构体可能更直观)。
QPainter的使用要保证save()与restore()成对出现,即开始会之前调用下save(),绘制完成后调用下restore()。
2,搜索与刷新
//刷新按钮
void on_refreshBt_clicked()
{
//获取城市天气数据
GetWeatherInfo(m_pManager);
//得到新数据后调用下绘制曲线的控件的刷新,进行绘制
ui->curveLb->update();
}
//搜索按钮
void on_searchBt_clicked()
{
//保存当前城市,当搜索出现错误时,还可以继续显示当前城市
m_strCityTemp=m_strCity;
m_strCity=ui->cityLineEdit->text();
GetWeatherInfo(m_pManager);
}
其中m_pManager是QNetworkAccessManager的一个指针,是网络编程部分的,与其相关的还有另外两个类,大致关系为
对于一个应用程序来说,一个QNetworkAccessManager已经足够了。
以刷新功能为例,通过自己实现的方法GetWeatherInfo去向服务器发送请求获取数据,通过m_pManager的信号得知服务器应答完成并处理数据,内容如下:
//初始化时绑定的信号槽
connect(m_pManager,&QNetworkAccessManager::finished,
this,&MainWindow::replyFinished);
//GetWeatherInfo的实现
void GetWeatherInfo(QNetworkAccessManager*& manager)
{
//根据城市获取城市邮编
QString strCityCode=m_tool[m_strCity];
//获取失败 或 输入为省份 或 没有覆盖到该城市
if(strCityCode=="000000000")
{
QMessageBox::warning(this,u8"错误",u8"天气:指定城市不存在!",QMessageBox::Ok);
return;
}
//拼好url:链接+城市邮编
QUrl jsonUrl(m_strUrl+strCityCode);
//发送网络请求
manager->get(QNetworkRequest(jsonUrl));
}
//网络请求完成后的响应槽函数
void replyFinished(QNetworkReply* reply)
{
//获取响应的消息 状态码200表示正常
QVariant status_code=reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
//出错
if(reply->error()!=QNetworkReply::NoError || status_code!=200)
{
QMessageBox::warning(this,u8"错误",u8"天气:请求数据错误,检查网络连接!",QMessageBox::Ok);
return;
}
//获取数据成功 读取全部数据
QByteArray bytes=reply->readAll();
//解析json数据
ParseJson(bytes);
}
3,QJson的读取与解析
如果前面从网络获取数据成功的话,QByteArray bytes=reply->readAll();bytes就会那到如下样式的内容:
将这些乱乱的数据处理一下,看起来直观些:
篇幅有限就不截全图了,现在key和value就可以非常直观的看出来了(安利一个json解析的链接:https://www.json.cn/)
然后通过ParseJson(bytes)函数去解析数据:
void ParseJson(QByteArray &bytes)
{
//首先将bytes数据转换成一个document对象
QJsonParseError err;
QJsonDocument jsonDoc = QJsonDocument::fromJson(bytes,&err);
//转换出错
if(err.error!=QJsonParseError::NoError)
return;
//
QJsonObject jsonObj=jsonDoc.object();
QString message=jsonObj.value("message").toString();
//返回false说明message中没有包含success 即失败
if(message.contains("success")==false)
{
QMessageBox::information(this,tr("The Information Of Json_desc"),u8"天气:城市错误!",QMessageBox::Ok);
m_strCity=m_strCityTemp;
return;
}
m_today=jsonObj;
//解析data中的yesterday
QJsonObject dataObj=jsonObj.value("data").toObject();
m_forecast[0]=dataObj.value("yesterday").toObject();
//解析data中的forecast
QJsonArray forecastArr=dataObj.value("forecast").toArray();
//j=0是因为从网上取到的预报数据中是不存在昨天的 即索引0处就是今天 1是明天...
int j=0;
for(int i=1;i<6;++i)
{//索引0处表示的是昨天的信息 所以应继续从索引1处开始取数据
m_forecast[i]=forecastArr.at(j).toObject();
j++;
}
//更新UI界面数据
SetLabelContent();
}
另外项目中在获取城市天气预报的服务中使用的是城市的邮政编码,所以还存在一个输入城市输出邮政编码的方法:
//构造时会去缓存城市名称-城市邮政编码的map
WeatherTool()
{
//获取当前程序的相对路径 继而得到城市邮编文件
QString fileName=QCoreApplication::applicationDirPath();
fileName+="/citycode-2021-06-23.json";
//用文本形式以只读的方式打开文件 将数据读取到json中,关闭文件
QFile file(fileName);
file.open(QIODevice::ReadOnly|QIODevice::Text);
QByteArray json=file.readAll();
file.close();
//按照文件的内容及json的规定 可以将数据分解成一个由城市组成的数组,然后遍历该数组
QJsonParseError err;
QJsonDocument jsonDoc=QJsonDocument::fromJson(json,&err);
QJsonArray citys=jsonDoc.array();
for(int i=0;i<citys.size();++i)
{
//将每个城市先转成一个对象,然后访问每个对象的key值为city_code处,得到value后转成string返回,其他同理
QString code=citys.at(i).toObject().value("city_code").toString();
QString city=citys.at(i).toObject().value("city_name").toString();
if(code.size()>0)
m_mapCityToCode[city]=code;
}
}
//传入城市 传出邮编
QString operator[](const QString& city)
{
std::map<QString,QString>::iterator iter=m_mapCityToCode.find(city);
if(iter==m_mapCityToCode.end())
iter=m_mapCityToCode.find(city+u8"市");
if(iter!=m_mapCityToCode.end())
return iter->second;
return "000000000";
}
配置的json文档大致如下:
4,信号与槽
类似于MFC的消息,但要比MFC的消息灵活很多,绑定的信号槽的语法为:
connect(信号发送者,&发了什么信号,信号接收者,&处理信号的函数)
以button为例,基础的用法为:
connect(pBtn,&QPushButton::clicked,this,&ClassName::OnBtnClicked)
信号与槽都为void类型,参数可有可无,信号只需定义无需实现;
信号可以传递,也可绑定多个槽;
信号的参数≥槽的参数,但相等的部分的参数类型必须一一对应。
5,Qt对象树的概念
Qt中的QObject会用对象树管理自己,它会用一个私有变量QList<QObject*>存储子孙后代,如创建一个QObject对象并指定父指针时,就会保存它们的父子关系(这里的父子关系与类的继承无关)。当父对象析构时,会直接析构掉其下的所有子对象。
但事物都有两面性,有好就有坏,针对于对象树,其好处也恰恰是坏处。
如下代码,运行后必崩溃:
关于崩溃的解释:
在函数内声明的临时变量是存储在栈上的,先入后出,所以当main函数执行结束后,会先释放Widget的对象w,而button在声明时已指定了父对象为w,所以当释放w的时候会直接释放掉button;当w释放完成后,栈会去释放button,就会二次析构,又因为button中有些data是new出来的,所以需要delete,所以这也就是为什么崩溃报错在heap上了。