项目介绍
本项目基于QT平台开发的一款天气预报的应用,效果图如下:
1、 有城市的天气预报,有背景图、控件半透明化。
2、 显示日期,城市名称,当天的天气预报
3、 当天天气预报的详细数据
4、 该天的一些生活指数:如感冒指数、每日寄语
5、 当天的日出日落时间,及扇形时间占比
6、 该城市,前一后四天的天气预报,含有日期,星期,天气,高低温
7、 最近一周的温湿度曲线
8、 搜索框、刷新按钮。
9、 窗口大小固定,无最大、最小化、关闭按钮。鼠标拖动窗口移动,右键退出,背景音乐。
10、自定义按钮图标
实现思路
实现过程
- 鼠标拖动窗口
mouseMoveEvent是鼠标移动的操作事件,mousePressEvent是按下鼠标的事件。添加私有成员:mPos做鼠标按下事件的坐标点记录(需要头文件:#include )
globalPos()获取鼠标在窗口的全局坐标,也可以理解为屏幕上的坐标。而pos()获取到的是应用程序窗体的坐标。要移动窗口操作的是程序相对屏幕原点的的坐标,用当前鼠标按下得到的屏幕坐标减去程序坐标,得到就是程序相对屏幕的一个坐标。
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();
}
- 读取城市Json文件
读取citycode-2019-08-23.json里面所有的内容,将所有的城市名称和城市id保存在一个map当中。<”城市”,“City_Code”>,这样就可以通过城市名称直接得到城市的code了。citycode-2019-08-23.json是一个Json文件,那么我们需要用到QJson来读取和解析
//将解析到的城市都存在这个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将文本格式转换为QJsonDocumentQJsonDocument 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个0return "000000000";
- 请求天气API数据
原理:使用QNetworkAccessManager从网上get对应的数据
// 请求天气API信息url = "http://t.weather.itboy.net/api/weather/city/"; //天气预报请求apicity = 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()<<bytes;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中的yesterdayQJsonObject 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的绘图指令。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.5pen.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);
- 温度曲线的绘制
根据控件的尺寸大小,选择45作为高温曲线参考点,以及长度找出六天的x坐标,计算出高温的平均值,用每天的高温天气减去平均值在乘以一个值作为对应的y值,平均值减去低温值乘以一个值作为低温曲线对应点的y值。昨天和当日的连线是虚线需单独进行绘制。
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数据的解析,定时器,事件过滤器,图形的绘制,背景音乐的添加,功能的封装等。