本项目基于QT平台开发的一款天气预报的应用,效果图如下:
1、 有城市的天气预报,有背景图、控件半透明化。
2、 显示日期,城市名称,当天的天气预报
3、 当天天气预报的详细数据
4、 该天的一些生活指数:如感冒指数、每日寄语
5、 当天的日出日落时间,及扇形时间占比
6、 该城市,前一后四天的天气预报,含有日期,星期,天气,高低温
7、 最近一周的温湿度曲线
8、 搜索框、刷新按钮。
9、 窗口大小固定,无最大、最小化、关闭按钮。鼠标拖动窗口移动,右键退出,背景音乐。
10、自定义按钮图标
void mouseMoveEvent(QMouseEvent *event);
void mousePressEvent(QMouseEvent *event);
//窗口移动计算
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
move(event->globalPos()-mPos);
}
void MainWindow::mousePressEvent(QMouseEvent *event)
{
mPos=event->globalPos()-this->pos();
}
//将解析到的城市都存在这个map中
std::map<QString,QString>m_mapCity2Code;
//获取当前程序运行的路径
QString fileName = QCoreApplication::applicationDirPath();
//创建错误信息收集的对象
QJsonParseError err;
//构建文件所在的路径。因为citycode-2019-08-23.json,在项目文件夹下
//也可以将它复制到项目运行的目录下,就不需要这么先返回两级目录
fileName+="/citycode-2019-08-23.json";
QFile file(fileName);
//打开文件
file.open(QIODevice::ReadOnly|QIODevice::Text);//只读用文本的方式读取
QByteArray json = file.readAll();
file.close();
//读取Json数据并用fromJson将文本格式转换为QJsonDocument
QJsonDocument jsonDoc = QJsonDocument::fromJson(json,&err);
//获取城市列表数组
QJsonArray citys = jsonDoc.array();//转换成array类型
for(int i=0;i<citys.size();i++)
{
QString code = citys.at(i).toObject().value("city_code").toString();
QString city = citys.at(i).toObject().value("city_name").toString();
//省份的code是空的,所以城市代码长度大于0的都可以添加的map中
if(code.size()>0)
m_mapCity2Code.insert(std::pair<QString,QString>(city,code));
}
}
//重载操作符[“city”]。根据城市名称直接得到城市代码:
QString operator[](const QString& city)
{
//为了很好的匹配,设置了直接搜索城市名字
std::map<QString,QString>::iterator it = m_mapCity2Code.find(city);
if(it==m_mapCity2Code.end()){
//搜索城市后戴市字
it = m_mapCity2Code.find(city+"市");
}
if(it==m_mapCity2Code.end()){
//搜索城市名后带县字
it = m_mapCity2Code.find(city+"县");
}
if(it!=m_mapCity2Code.end())
//找到直接返回城市代码
return it->second;
//没找到返回9个0
return "000000000";
// 请求天气API信息
url = "http://t.weather.itboy.net/api/weather/city/"; //天气预报请求api
city = u8"长沙";//默认访问的城市
cityTmp = city;//临时存放城市变量,防止输入错误城市的时候,原来的城市名称还在。
manager = new QNetworkAccessManager(this);//new的一个QNetworkAccessManager对象
connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replayFinished(QNetworkReply*)));
getWeatherInfo(manager);
//为了便于后面的刷新,和切换城市,请求数据是需要重复执行的,所以我们将请求数据的部分封装成函数,便于后面使用
//请求数据
//只要[ ]操作得到的不是0代码的城市,就将代码加到url的后面,然后使用QNetworkAccessManager对象manager来get天气预报数据,到这里,只是发送了一个get请求,到底说明时候结束,我们这里不管。后续的事情,当信请求结束的清号产生之后,让槽函数去做。
void MainWindow::getWeatherInfo(QNetworkAccessManager *manager)
{
QString citycode = tool[city];
if(citycode=="000000000"){
QMessageBox::warning(this, u8"错误", u8"天气:指定城市不存在!", QMessageBox::Ok);
return;
}
QUrl jsonUrl(url + citycode);
manager->get( QNetworkRequest(jsonUrl) );
}
void MainWindow::replayFinished(QNetworkReply *reply)
{
/* 获取响应的信息,状态码为200表示正常 */
//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();//使用QByteArray字节数组来读取数据
//qDebug()<
parseJson(bytes);
}
connect这一行的解释:因为我们的请求,不是马上就能有结果的,网络就算很快,也要几毫秒的时间,但我们的程序是很快的。所以在这里我们有一个信号是:finished(QNetworkReply*),就是当请求结束之后,就会有一个信号发生,我们无论网络的好坏,快慢,只要这个请求结束了,就会产生这么一个信号,写一个槽函数,在请求完成之后执行槽函数,在槽函数里去读取请求到的数据,不然我们不知道在哪里写读取数据的代码。这里也可以理解成一个准备工作。当做好这些准备之后,我们就可以开始请求数据了
4.解析天气数据
用法:
QJsonObject:代表一个Json对象,包含多个键-值对;
QJsonArray:代表一个Json数组,可用下标的方法遍历,具体的值可为QJsonValue类型;
QJsonValue:代表具体的值,值可以是QJsonObject,也可以是QJsonArray(可用isObject()和isArray()方法来判断),或者其他类型;
QJsonDocument:提供读写一个Json文档的方法,一个 JSON 文档可以使用QJsonDocument::fromJson() 从基于文本的表示转化为 QJsonDocument, toJson() 则可以反向转化为文本。解析器非常快且高效,并将 JSON 转换为 Qt 使用的二进制表示。
QJsonParseError:存储解析Json过程中出现的错误
void MainWindow::parseJson(QByteArray&bytes)
{
QJsonParseError err;
QJsonDocument jsonDoc = QJsonDocument::fromJson(bytes, &err); // 检测json格式
if (err.error != QJsonParseError::NoError) // Json格式错误
{
return;
}
//将json文档转换成json对象。并提取message字段中的类容,进而转成字符串的形式
QJsonObject jsObj=jsonDoc.object();
QString message=jsObj.value("message").toString();
//Contains就是检查这个字符串里有没有某个字段,其实就是搜索的意思,看message里有没有success,没有说明请求的城市是错误的,恢复上次一次的城市。
if (message.contains("success")==false)
{
QMessageBox::information(this, tr("The information of Json_desc"), u8"天气:城市错误!", QMessageBox::Ok );
city = cityTmp;
return;
}
today = jsObj;//today保存当天的天气数据。
// 解析data中的yesterday
QJsonObject dataObj = jsObj.value("data").toObject();
forecast[0] = dataObj.value("yesterday").toObject();
// 解析data中的forecast即后面五天的天气信息
QJsonArray forecastArr = dataObj.value("forecast").toArray();
//forecastArr里面的第一组数据是当日的数据,而forecast里面的是前一天的数据
int j = 0;
for (int i = 1; i < 6; i++)
{
forecast[i] = forecastArr.at(j).toObject();
j++;
}
//调用函数进行数据的更新
setLabelContent();
}
void MainWindow::setLabelContent()
{
// 今日数据
ui->dateLb->setText(today.date);
ui->temLb->setText(today.wendu);
ui->cityLb->setText(today.city);
ui->typeLb->setText(today.type);
ui->noticeLb->setText(today.notice);
ui->shiduLb->setText(today.shidu);
ui->pm25Lb->setText(today.pm25);
ui->fxLb->setText(today.fx);
ui->flLb->setText(today.fl);
ui->ganmaoBrowser->setText(today.ganmao);
//更新六天数据
// 六天数据
for (int i = 0; i < 6; i++)
{
forecast_week_list[i]->setText(forecast[i].week.right(3));
forecast_date_list[i]->setText(forecast[i].date.left(3));
forecast_type_list[i]->setText(forecast[i].type);
forecast_high_list[i]->setText(forecast[i].high.split(" ").at(1));
forecast_low_list[i]->setText(forecast[i].low.split(" ").at(1));
forecast_typeIco_list[i]->setStyleSheet( tr("image: url(:/day/day/%1.png);").arg(forecast[i].type) );
if (forecast[i].aqi.toInt() >= 0 && forecast[i].aqi.toInt() <= 50)
{
forecast_aqi_list[i]->setText(u8"优质");
forecast_aqi_list[i]->setStyleSheet("color: rgb(0, 255, 0);");
}
else if (forecast[i].aqi.toInt() > 50 && forecast[i].aqi.toInt() <= 100)
{
forecast_aqi_list[i]->setText(u8"良好");
forecast_aqi_list[i]->setStyleSheet("color: rgb(255, 255, 0);");
}
else if (forecast[i].aqi.toInt() > 100 && forecast[i].aqi.toInt() <= 150)
{
forecast_aqi_list[i]->setText(u8"轻度污染");
forecast_aqi_list[i]->setStyleSheet("color: rgb(255, 170, 0);");
}
else if (forecast[i].aqi.toInt() > 150 && forecast[i].aqi.toInt() <= 200)
{
forecast_aqi_list[i]->setText(u8"重度污染");
forecast_aqi_list[i]->setStyleSheet("color: rgb(255, 0, 0);");
}
else
{
forecast_aqi_list[i]->setText(u8"严重污染");
forecast_aqi_list[i]->setStyleSheet("color: rgb(170, 0, 0);");
}
}
//设置昨天和今天
ui->week0Lb->setText( u8"昨天" );
ui->week1Lb->setText( u8"今天" );
//温度曲线的绘制
ui->curveLb->update();
}
日出日落的绘制
在QT中绘制图形要用到QPainter类
几个常用的绘制角色:
QPen : 用于绘制几何图形的边缘,由颜色,宽度,线风格等参数组成
QBrush : 用于填充几何图形的调色板,由颜色和填充风格组成
QFont : 用于文本绘制
QPixmap : 绘制图片,可以加速显示,带有屏幕截图,窗口截图等支持,适合小图片
QImage : 绘制图片,可以直接读取图像文件进行像素访问,适合大图片
QBitmap : QPixmap的一个子类,主要用于显示单色位图
QPicture : 绘图装置,用于记录和重播Qpainter的绘图指令。
1.绘制直线
// 日出日落底线
const QPoint Weather::sun[2] = {
QPoint(20, 75),
QPoint(130, 75)
};
QPainter painter(ui->sunRiseSetLb);
// 反锯齿设置
painter.setRenderHint(QPainter::Antialiasing, true);
painter.save();//保存当前绘制器状态(将状态推送到堆栈上)
QPen pen = painter.pen();
pen.setWidthF(0.5);
pen.setColor(Qt::yellow);
painter.setPen(pen);
painter.drawLine(sun[0], sun[1]);//绘制直线
painter.restore();
2.绘制文字和圆弧
QPoint这个点,构造就是两个参数x,y坐标,而这个QRect 代表是是区域,并在指定区域内进行绘制,也可以理解成一个矩形
QRect总共四个参数:
left:上左方,矩形的左边x坐标,图中的A点。
top:矩形顶部的y坐标,图中的B点
width:矩形的右边x坐标,图中的C点
height:矩形底部的y坐标,图中的D点
// 日出日落时间
const QRect Weather::sunRizeSet[2] = {
QRect(0, 80, 50, 20),
QRect(100, 80, 50, 20)
};
// 日出日落圆弧
const QRect Weather::rect[2] = {
QRect(25, 25, 100, 100), // 虚线圆弧
QRect(50, 80, 50, 20) // “日出日落”文本
painter.save();
painter.setFont( QFont("Microsoft Yahei", 8, QFont::Normal) ); // 字体、大小、正常粗细
painter.setPen(Qt::white);
if (today.sunrise != "" && today.sunset != "")
{
//在指定区域内绘制文字
painter.drawText(sunRizeSet[0], Qt::AlignHCenter, today.sunrise);
painter.drawText(sunRizeSet[1], Qt::AlignHCenter, today.sunset);
}
painter.drawText(rect[1], Qt::AlignHCenter, u8"日出日落");//在指定区域绘制文字
painter.restore();
// 绘制圆弧
painter.save();
pen.setWidthF(0.5); //设置线条的宽度0.5
pen.setStyle(Qt::DotLine); //虚线
pen.setColor(Qt::green); //设置颜色
painter.setPen(pen);
painter.drawArc(rect[0], 0 * 16, 180 * 16); //绘制圆弧
painter.restore();
drawArc:绘制圆弧三个参数,绘制的区域,起始角度,终止角度。
但是Qt的角度的基数是16分之一度,所以在任何话圆弧的地方,我们都要乘以16才得到我们正常的度数。
3.绘制日出日落占比
绘制日出日落的占比就是通过当前时间与日出时间的差和日落时间与日出时间的差的比值来进行绘制,由于时间在时刻变化,因此使用了定时器,对这个占比进行改变绘制。
// 绘制日出日落占比
if (today.sunrise != "" && today.sunset != "")
{
painter.setPen(Qt::NoPen);//关闭笔
painter.setBrush(QColor(255, 85, 0, 100));//使用画刷
int startAngle, spanAngle;//开始时间和结束时间
QString sunsetTime = today.date + " " + today.sunset;
//如果当前的时间,已经晚于日落的时间,那么肯定是180度全画上了
if (QDateTime::currentDateTime() > QDateTime::fromString(sunsetTime, "yyyy-MM-dd hh:mm"))
{
startAngle = 0 * 16;
spanAngle = 180 * 16;
}
else
{
// 计算起始角度和跨越角度
static QStringList sunSetTime = today.sunset.split(":");
static QStringList sunRiseTime = today.sunrise.split(":");
static QString sunsetHour = sunSetTime.at(0);
static QString sunsetMint = sunSetTime.at(1);
static QString sunriseHour = sunRiseTime.at(0);
static QString sunriseMint = sunRiseTime.at(1);
static int sunrise = sunriseHour.toInt() * 60 + sunriseMint.toInt();
static int sunset = sunsetHour.toInt() * 60 + sunsetMint.toInt();
int now = QTime::currentTime().hour() * 60 + QTime::currentTime().minute();
startAngle = ( (double)(sunset - now) / (sunset - sunrise) ) * 180 * 16;
spanAngle = ( (double)(now - sunrise) / (sunset - sunrise) ) * 180 * 16;
}
if (startAngle >= 0 && spanAngle >= 0)
{
painter.drawPie(rect[0], startAngle, spanAngle); // 扇形绘制
}
}
//定时器,实时更新数据
sunTimer=new QTimer(ui->sunRiseSetLb);
connect(sunTimer,SIGNAL(timeout()),ui->sunRiseSetLb,SLOT(update()));
sunTimer->start(1000);
void MainWindow::paintCurve()
{
QPainter painter(ui->curveLb);
painter.setRenderHint(QPainter::Antialiasing, true); // 反锯齿
int tempTotal = 0;
int high[6] = {};
int low[6] = {};
//计算平均值
QString h, l;
for (int i = 0; i < 6; i++)
{
h = forecast[i].high.split(" ").at(1);
h = h.left(h.length() - 1);
high[i] = (int)(h.toDouble());
tempTotal += high[i];
l = forecast[i].low.split(" ").at(1);
l = l.left(h.length() - 1);
low[i] = (int)(l.toDouble());
}
int tempAverage = (int)(tempTotal / 6); // 最高温平均值
// 算出温度对应坐标
int pointX[6] = {35, 103, 172, 241, 310, 379}; // 点的X坐标
int pointHY[6] = {0};
int pointLY[6] = {0};
for (int i = 0; i < 6; i++)
{
pointHY[i] = TEMPERATURE_STARTING_COORDINATE - ((high[i] - tempAverage) * SPAN_INDEX);
pointLY[i] = TEMPERATURE_STARTING_COORDINATE + ((tempAverage - low[i]) * SPAN_INDEX);
}
QPen pen = painter.pen();
pen.setWidth(1); //设置笔的宽度为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], pointHY[0]), ORIGIN_SIZE, ORIGIN_SIZE);
painter.drawEllipse(QPoint(pointX[1], pointHY[1]), ORIGIN_SIZE, ORIGIN_SIZE);
painter.drawLine(pointX[0], pointHY[0], pointX[1], pointHY[1]);
//今天到未来
pen.setStyle(Qt::SolidLine);
pen.setWidth(1);
painter.setPen(pen);
for (int i = 1; i < 5; i++)
{ //先绘制点在绘制线
painter.drawEllipse(QPoint(pointX[i+1], pointHY[i+1]), ORIGIN_SIZE, ORIGIN_SIZE);
painter.drawLine(pointX[i], pointHY[i], pointX[i+1], pointHY[i+1]);
}
painter.restore();
// 低温曲线绘制
pen.setColor(QColor(0, 255, 255));
pen.setStyle(Qt::DotLine);
painter.setPen(pen);
painter.setBrush(QColor(0, 255, 255));
painter.drawEllipse(QPoint(pointX[0], pointLY[0]), ORIGIN_SIZE, ORIGIN_SIZE);
painter.drawEllipse(QPoint(pointX[1], pointLY[1]), ORIGIN_SIZE, ORIGIN_SIZE);
painter.drawLine(pointX[0], pointLY[0], pointX[1], pointLY[1]);
pen.setColor(QColor(0, 255, 255));
pen.setStyle(Qt::SolidLine);
painter.setPen(pen);
for (int i = 1; i < 5; i++)
{
painter.drawEllipse(QPoint(pointX[i+1], pointLY[i+1]), ORIGIN_SIZE, ORIGIN_SIZE);
painter.drawLine(pointX[i], pointLY[i], pointX[i+1], pointLY[i+1]);
}
}
本项目总体思路就是添加相应的控件,通过一个接口从网上拿取对应的数据,对拿到的数据进行解析,并将解析到的数据更新到对应的控件上。
运用到的技术有QT基本控件的使用,QT类的使用,网络数据的请求,json数据的解析,定时器,事件过滤器,图形的绘制,背景音乐的添加,功能的封装等。