qt开发 天气预报
前言
练手项目
代码参考b站https://www.bilibili.com/video/BV1GW42197ff视频
软件内图标来自https://www.iconfont.cn/阿里巴巴阿里巴巴矢量图标库
主要包含以下几个核心功能:
网络请求:通过网络接口获取天气数据。
数据解析:将获取到的 JSON 格式的天气数据进行解析。
界面更新:把解析后的数据展示在应用界面上。
图表绘制:绘制最高温度和最低温度的折线图,并在图上显示具体的温度数值。
正文
UI
UI绘制没什么好说的,就是拉控件,然后用网上的res修改图标样式,改排版,大部分都是label,一个push button(搜索按钮),一个lineEdit(搜索栏)
我在代码调试中换了图标,原因是下载的图片有正方形的有长方形的,不统一不好适应UI,虽然图标样式很好看但是还是换过了一套。
主要代码
1. 网络请求与数据获取
在 weatherRead 函数中,我们使用 QNetworkAccessManager 来发起网络请求,向天气 API 发送请求并获取数据。以下是代码:
void Widget::weatherRead()
{
manager = new QNetworkAccessManager(this);
// 天气 API 的 URL
strUrl = "http://gfeljm.tianqiapi.com/api?unescape=1&version=v9&appid=83941685&appsecret=Py2be4hk";
QNetworkRequest res(strUrl);
manager->get(res);
reply = manager->get(res);
// 连接信号与槽,当请求完成时调用 readHttpReply 函数
connect(manager,&QNetworkAccessManager::finished,this,&Widget::readHttpReply);
// 初始化一些界面元素列表,用于后续更新界面
mDateList << ui->labelday0 << ui->labelday1 << ui->labelday2 << ui->labelday3 << ui->labelday4 << ui->labelday5;
mWeekList << ui->labeldate0 << ui->labeldate1 << ui->labeldate2 << ui->labeldate3 << ui->labeldate4 << ui->labeldate5;
mIconList << ui->labelweather0 << ui->labelweather1 << ui->labelweather2 << ui->labelweather3 << ui->labelweather4 << ui->labelweather5;
mWeaTypeList << ui->labelWeather0 << ui->labelWeather1 << ui->labelWeather2 << ui->labelWeather3 << ui->labelWeather4 << ui->labelWeather5;
mAirqList << ui->labelairq0 << ui->labelairq1 << ui->labelairq2 << ui->labelairq3 << ui->labelairq4 << ui->labelairq5;
mFxList << ui->labelFX0 << ui->labelFX1 << ui->labelFX2 << ui->labelFX3 << ui->labelFX4 << ui->labelFX5;
// 建立天气类型与对应图标资源的映射
mTypeMap.insert("晴", ":/res/晴.png");
mTypeMap.insert("多云", ":/res/多云.png");
mTypeMap.insert("大雪", ":/res/大雪.png");
mTypeMap.insert("大雨", ":/res/大雨.png");
mTypeMap.insert("雷暴", ":/res/雷暴.png");
mTypeMap.insert("雷雨", ":/res/雷雨.png");
mTypeMap.insert("小雪", ":/res/小雪.png");
mTypeMap.insert("小雨", ":/res/小雨.png");
mTypeMap.insert("阴", ":/res/阴.png");
mTypeMap.insert("雨夹雪", ":/res/雨夹雪.png");
mTypeMap.insert("中雪", ":/res/中雪.png");
mTypeMap.insert("中雨", ":/res/中雨.png");
mTypeMap.insert("雾", ":/res/雾.png");
// 为两个用于绘制折线图的 widget 安装事件过滤器
ui->widget0404->installEventFilter(this);
ui->widget0405->installEventFilter(this);
}
上述代码可以看出其实我没有用多少图标资源,有些天气的图标没有适配,没办法,阿里巴巴上面天气套图火的我都看过了,总是缺少一些图片,不得已只能将就着用了。
在 weatherRead() 函数中, ui->widget0404 和 ui->widget0405 安装了事件过滤器,Widget 类会拦截 ui->widget0404 和 ui->widget0405 的事件。当这两个 widget 接收到重绘事件(QPaintEvent)时,会先经过 Widget 类的 eventFilter() 方法处理。
在这个函数中,我们创建了一个 QNetworkAccessManager 对象来管理网络请求。通过 QNetworkRequest 设置请求的 URL,然后使用 manager->get(res) 发起 GET 请求。同时,我们还将 manager 的 finished 信号连接到 readHttpReply 槽函数,以便在请求完成时进行处理。(AI总结)
2. 数据解析
当网络请求完成后,readHttpReply 函数会被调用,在该函数中我们会对返回的数据进行解析。以下是代码:
void Widget::readHttpReply(QNetworkReply *reply)
{
// 获取 HTTP 响应状态码
int resCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if(reply->error() == QNetworkReply::NoError && resCode == 200)
{
// 读取响应数据
QByteArray data = reply->readAll();
// 调用解析函数解析数据
parseWeatherJsonData(data);
}
else
{
// 若请求出错,弹出错误提示框
QMessageBox mes;
mes.setWindowTitle("错误");
mes.setText("网络请求失败");
mes.setStandardButtons(QMessageBox::Ok);
mes.exec();
}
}
void Widget::parseWeatherJsonData(QByteArray rawData)
{
// 将原始数据转换为 JSON 文档
QJsonDocument jsonDoc = QJsonDocument::fromJson(rawData);
if(!jsonDoc.isNull() && jsonDoc.isObject())
{
QJsonObject jsonRoot = jsonDoc.object();
// 获取当前城市名称
days[0].mCity = jsonRoot["city"].toString();
// 获取当前 PM2.5 数值
days[0].mPm25 = jsonRoot["aqi"].toObject()["pm25"].toString();
if(jsonRoot.contains("data") && jsonRoot["data"].isArray())
{
QJsonArray weaArray = jsonRoot["data"].toArray();
for(int i = 0; i < weaArray.size(); i ++)
{
QJsonObject obj = weaArray[i].toObject();
// 解析每天的天气数据
days[i].mDate = obj["date"].toString();
days[i].mWeek = obj["week"].toString();
days[i].mWeathType = obj["wea"].toString();
days[i].mTemp = obj["tem"].toString();
days[i].mTempLow = obj["tem2"].toString();
days[i].mTempHigh = obj["tem1"].toString();
days[i].mFx = obj["win"].toArray()[0].toString();
days[i].mFl = obj["win_speed"].toString();
days[i].mAirq = obj["air_level"].toString();
days[i].mTips = obj["air_tips"].toString();
days[i].mHu = obj["humidity"].toString();
}
// 解析完成后更新界面
updateUI();
}
}
}
此处代码就是使用我设置好的api去获取json数据,然后存到我设置好的days数组中,方便更新ui。
在 readHttpReply 函数中,我们首先检查响应是否成功(状态码为 200 且无错误),如果成功则读取响应数据并调用 parseWeatherJsonData 函数进行解析。在 parseWeatherJsonData 函数中,我们使用 QJsonDocument 和 QJsonObject 来解析 JSON 数据,将解析后的数据存储在 days 数组中,最后调用 updateUI 函数更新界面。(AI总结)
3. 界面更新
updateUI 函数负责将解析后的数据显示在界面上。以下是代码:
void Widget::updateUI()
{
// 更新当前日期和星期
ui->labelCurrentDate->setText(days[0].mDate+" "+days[0].mWeek);
// 更新城市名称
ui->labelCity->setText(days[0].mCity+"市");
// 更新当前温度
ui->labelTemp->setText(days[0].mTemp+"℃");
// 更新最低最高温度范围
ui->labelTempRange->setText(days[0].mTempLow+"~"+days[0].mTempHigh+"℃");
// 更新天气类型和图标
ui->labelWeathertype->setText(days[0].mWeathType);
ui->labelWeathericon->setPixmap(mTypeMap[days[0].mWeathType]);
// 更新感冒指数
ui->labelGanmao->setText(days[0].mTips);
// 更新风向
ui->labelFXType01->setText(days[0].mFx);
// 更新风力
ui->labelFXType02->setText(days[0].mFl);
// 更新 PM2.5 数值
ui->labelPMType02->setText(days[0].mPm25);
// 更新湿度
ui->labelSDType02->setText(days[0].mHu);
// 更新空气质量
ui->labelZLType02->setText(days[0].mAirq);
for(int i = 0; i < 6; i++)
{
// 更新未来几天的星期
mWeekList[i]->setText(days[i].mWeek);
// 处理日期格式并更新
QStringList daylist = days[i].mDate.split("-");
mDateList[i]->setText(daylist.at(1)+"-"+daylist.at(2));
// 处理天气类型并更新图标
QStringList iconList = days[i].mWeathType.split("转");
mIconList[i]->setPixmap(mTypeMap[iconList.at(0)]);
// 更新天气类型文字描述
mWeaTypeList[i]->setText(days[i].mWeathType);
// 更新空气质量
QString airQ = days[i].mAirq;
mAirqList[i]->setText(airQ);
if(airQ == "优")
{
// 若空气质量为优,设置绿色背景
mAirqList[i]->setStyleSheet("background-color: rgb(116, 168, 0); border-radius: 5px;");
}
else
{
// 否则设置橙色背景
mAirqList[i]->setStyleSheet("background-color: rgb(252, 183, 16); border-radius: 5px;");
}
// 更新风向和风力
mFxList[i]->setText(days[i].mFx+"\n"+days[i].mFl);
}
// 强制界面更新
update();
}
这一段主要是更新了UI上面的界面,然后调用update()触发重绘,继而更新下面的UI。
在这个函数中,我们将 days 数组中的数据依次更新到界面的各个控件上,包括日期、温度、天气类型、空气质量等。同时,根据空气质量的不同,为空气质量显示控件设置不同的背景颜色。(AI总结)
4. 折线图绘制
我们使用 QPainter 来绘制最高温度和最低温度的折线图,并在图上显示具体的温度数值。以下是绘制最高温度折线图的代码:
void Widget::drawTempLineHigh()
{
QPainter painter(ui->widget0404);
// 设置橙色画笔和画刷,用于绘制最高温度折线
QColor highColor(255, 165, 0);
painter.setBrush(highColor);
painter.setPen(highColor);
int ave = 0;
int sum = 0;
int offSet;
int middle = ui->widget0404->height() / 2;
// 计算最高温度的平均值
for (int i = 0; i < 6; i++)
{
bool ok;
int tempHigh = days[i].mTempHigh.toInt(&ok);
if (ok)
{
sum += tempHigh;
}
else
{
qDebug() << "Failed to convert mTempHigh to int for index" << i;
}
}
if (sum > 0)
{
ave = sum / 6;
}
QPoint points[6];
for (int i = 0; i < 6; i++)
{
bool ok;
int tempHigh = days[i].mTempHigh.toInt(&ok);
if (ok)
{
// 将 mAirqList[i] 的坐标转换到 ui->widget0404 的坐标系中
QPoint mappedPoint = ui->widget0404->mapFromGlobal(mAirqList[i]->mapToGlobal(QPoint(0, 0)));
points[i].setX(mappedPoint.x() + mAirqList[i]->width() / 2);
offSet = (tempHigh - ave) * 3;
points[i].setY(middle - offSet);
int x = points[i].x();
int y = points[i].y();
if (x >= 0 && x < ui->widget0404->width() && y >= 0 && y < ui->widget0404->height())
{
// 绘制椭圆点
painter.drawEllipse(points[i], 3, 3);
// 设置较小的字体
QFont smallFont = painter.font();
smallFont.setPointSize(smallFont.pointSize() - 2);
painter.setFont(smallFont);
// 在点上方合适位置绘制温度度数
QString tempText = QString::number(tempHigh) + "°C";
QFontMetrics fm(painter.font());
int textWidth = fm.horizontalAdvance(tempText);
int textHeight = fm.height();
// 调整 y 坐标偏移量,让文本位置低一点
QPoint textPos(x - textWidth / 2, y - textHeight / 2);
painter.drawText(textPos, tempText);
// 恢复原来的字体
painter.setFont(QFont());
}
else
{
qDebug() << "Ellipse position out of range for index" << i << ": (" << x << ", " << y << ")";
}
}
else
{
qDebug() << "Failed to convert mTempHigh to int for index" << i;
}
}
if (sum > 0)
{
// 绘制折线
painter.drawPolyline(points, 6);
}
}
这段代码我用AI做了辅助,主要是总是画不好伤脑筋,交给AI处理细节去了。展示的是最高温度的折线绘制代码,最低温度折线代码类似。
绘制最低温度折线图的 drawTempLineLow 函数与 drawTempLineHigh 函数逻辑类似,只是使用的颜色和数据不同。在绘制过程中,我们首先计算最高温度的平均值,然后根据每天的最高温度与平均值的差值计算每个点的坐标,绘制椭圆点和折线,并在点的上方合适位置显示具体的温度数值。(AI总结)
最终成果截图
难点解释(AI总结)
1. 网络请求与异步处理
在 Qt 中,网络请求是异步的,这意味着当我们发起一个网络请求后,程序不会等待请求完成就会继续执行后续代码。因此,我们需要使用信号与槽机制来处理请求完成的事件。在这个应用中,我们将 QNetworkAccessManager 的 finished 信号连接到 readHttpReply 槽函数,当请求完成时,readHttpReply 函数会被自动调用,从而实现异步处理。
2. JSON 数据解析
JSON 是一种常用的数据交换格式,在处理网络返回的 JSON 数据时,我们需要使用 Qt 提供的 QJsonDocument、QJsonObject 和 QJsonArray 等类来进行解析。首先,我们将原始的 JSON 数据转换为 QJsonDocument 对象,然后通过 QJsonObject 和 QJsonArray 来获取具体的数据。在解析过程中,需要注意数据的类型和结构,确保能够正确获取所需的数据。
3. 坐标转换与绘制
在绘制折线图时,我们需要将界面上的控件坐标转换到绘制区域的坐标系中,以确保绘制的点和折线位置正确。在这个应用中,我们使用 mapFromGlobal 和 mapToGlobal 方法来进行坐标转换。同时,在绘制文本时,需要考虑文本的大小和位置,确保文本能够正确显示在点的上方。