No.03 天气预报

版权声明:转载文章如有侵权,敬请联系删除!原创文章允许转载,但请注明出处!---凭栏听风雨 https://blog.csdn.net/WU9797/article/details/79100662

一 Qt版本

Qt Creator 4.4.1 Based on Qt 5.9.3 (MSVC 2015, 32 bit)

 

二 项目效果图

 

 

 

三 代码部分

pro文件添加 QT += network

3.1 头文件 weather.h

 

#ifndef WEATHER_H
#define WEATHER_H

#include <QWidget>
#include <QList>
#include <QLabel>
#include <QPoint>
#include <QRect>
#include <QTimer>
#include <QMouseEvent>
#include <QMenu>
#include <QAction>

#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QByteArray>

namespace Ui {
class Weather;
}

struct Today
{
    QString date;
    QString wendu;
    QString city;
    QString shidu;
    QString pm25;
    QString quality;
    QString ganmao;
    QString fx;
    QString fl;
    QString type;
    QString sunrise;
    QString sunset;
    QString notice;
};

struct Forecast
{
    QString date;
    QString high;
    QString low;
    QString aqi;
    QString type;
};

class Weather : public QWidget
{
    Q_OBJECT

public:
    explicit Weather(QWidget *parent = 0);
    ~Weather();

protected:
    bool eventFilter(QObject *watched, QEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void mousePressEvent(QMouseEvent *event);
    void contextMenuEvent(QContextMenuEvent *menuEvent);

private slots:
    void replayFinished(QNetworkReply *reply);
    void on_searchBt_clicked();
    void on_refreshBt_clicked();
    void slot_exitApp();

private:
    Ui::Weather *ui;

    QPoint mPos; // 窗口移动

    QList<QLabel *> forecast_week_list;
    QList<QLabel *> forecast_date_list;
    QList<QLabel *> forecast_aqi_list;
    QList<QLabel *> forecast_type_list;
    QList<QLabel *> forecast_typeIco_list;
    QList<QLabel *> forecast_high_list;
    QList<QLabel *> forecast_low_list;

    Today today;
    Forecast forecast[6];

    static const QPoint sun[2];
    static const QRect sunRizeSet[2];
    static const QRect rect[2];

    QTimer *sunTimer;

    QNetworkAccessManager *manager;
    QString url;
    QString city;
    QString cityTmp;

    /* 右键菜单退出程序 */
    QMenu *m_pMenu;
    QAction *m_pExitAct;

    void getWeatherInfo(QNetworkAccessManager *manager);
    void parseJson(QByteArray bytes);
    void setLabelContent();
    void paintSunRiseSet();
    void paintCurve();
    void callKeyBoard();
};

#endif // WEATHER_H
 

 

3.2 核心代码 weather.cpp

 

 

#include "weather.h"
#include "ui_weather.h"

//#include "keyboard.h"

#include <QPainter>
#include <QDateTime>
#include <QMessageBox>

#include <QJsonDocument>
#include <QJsonParseError>
#include <QJsonObject>
#include <QJsonValue>
#include <QJsonArray>

#define SPAN_INDEX 3 // 温度曲线间隔指数
#define ORIGIN_SIZE 3 // 温度曲线原点大小
#define TEMPERATURE_STARTING_COORDINATE 45 // 高温平均值起始坐标

// 日出日落底线
const QPoint Weather::sun[2] = {
    QPoint(20, 75),
    QPoint(130, 75)
};

// 日出日落时间
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) // “日出日落”文本
};

Weather::Weather(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Weather)
{
    ui->setupUi(this);

    this->setWindowFlag(Qt::FramelessWindowHint);

    ui->cityLineEdit->setStyleSheet("QLineEdit{border: 1px solid gray; border-radius: 4px; background:argb(47, 47, 47, 130); color:rgb(255, 255, 255);} QLineEdit:hover{border-color:rgb(101, 255, 106); }");

    forecast_week_list << ui->week0Lb << ui->week1Lb << ui->week2Lb << ui->week3Lb << ui->week4Lb << ui->week5Lb;
    forecast_date_list << ui->date0Lb << ui->date1Lb << ui->date2Lb << ui->date3Lb << ui->date4Lb << ui->date5Lb;
    forecast_aqi_list << ui->quality0Lb << ui->quality1Lb << ui->quality2Lb << ui->quality3Lb << ui->quality4Lb << ui->quality5Lb;
    forecast_type_list << ui->type0Lb << ui->type1Lb << ui->type2Lb << ui->type3Lb << ui->type4Lb << ui->type5Lb;
    forecast_typeIco_list << ui->typeIco0Lb << ui->typeIco1Lb << ui->typeIco2Lb << ui->typeIco3Lb << ui->typeIco4Lb << ui->typeIco5Lb;
    forecast_high_list << ui->high0Lb << ui->high1Lb << ui->high2Lb << ui->high3Lb << ui->high4Lb << ui->high5Lb;
    forecast_low_list << ui->low0Lb << ui->low1Lb << ui->low2Lb << ui->low3Lb << ui->low4Lb << ui->low5Lb;

    // 结构体初始化
    today.date = "0000-00-00";
    today.city = "null";
    today.fl = "无数据";
    today.fx = "无数据";
    today.ganmao = "无数据";
    today.notice = "无数据";
    today.pm25 = "无数据";
    today.quality = "无数据";
    today.shidu = "无数据";
    today.sunrise = "00:00";
    today.sunset = "00:00";
    today.wendu = "null";
    today.type = "无数据";

    for (int i = 0; i < 6; i++)
    {
        forecast[i].aqi = "0";
        forecast[i].date = "00日星期0";
        forecast[i].high = "高温 0.0℃";
        forecast[i].low = "低温 0.0℃";
        forecast[i].type = "undefined";
    }

    // 右键菜单
    m_pMenu = new QMenu(this);
    m_pExitAct = new QAction;
    m_pExitAct->setText( tr("退出") );
    m_pExitAct->setIcon(QIcon(":/weatherIco/close.ico"));
    m_pMenu->addAction(m_pExitAct);
    connect( m_pExitAct, SIGNAL(triggered(bool)),
             this, SLOT(slot_exitApp()) );

    // dateLb和WeekLb样式表设置
    for (int i = 0; i < 6; i++)
    {
        forecast_date_list[i]->setStyleSheet("background-color: rgba(0, 255, 255, 100);");
        forecast_week_list[i]->setStyleSheet("background-color: rgba(0, 255, 255, 100);");
    }

    // 请求天气API信息
    url = "http://www.sojson.com/open/api/weather/json.shtml?city=";
    city = "南岸";
    cityTmp = city;
    manager = new QNetworkAccessManager(this);
    connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replayFinished(QNetworkReply*)));
    getWeatherInfo(manager);

    /* 事件过滤 */
    ui->sunRiseSetLb->installEventFilter(this); // 启用事件过滤器
    ui->curveLb->installEventFilter(this);
    ui->cityLineEdit->installEventFilter(this);

    sunTimer = new QTimer(ui->sunRiseSetLb);
    connect(sunTimer, SIGNAL(timeout()), ui->sunRiseSetLb, SLOT(update()));
    sunTimer->start(1000);

//    setLabelContent();
}

Weather::~Weather()
{
    delete ui;
}

/* 请求数据 */
void Weather::getWeatherInfo(QNetworkAccessManager *manager)
{
    QUrl jsonUrl(url + city);
    manager->get( QNetworkRequest(jsonUrl) );
}

void Weather::replayFinished(QNetworkReply *reply)
{
    /* 获取响应的信息,状态码为200表示正常 --comment by wsg 2017/12/11 */
    QVariant status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);

    if(reply->error() != QNetworkReply::NoError || status_code != 200)
    {
        QMessageBox::warning(this, "错误", "天气:请求数据错误,检查网络连接!", QMessageBox::Ok);
        return;
    }

    QByteArray bytes = reply->readAll();
//    QString result = QString::fromLocal8Bit(bytes);
    parseJson(bytes);
}

/* 解析Json数据 */
void Weather::parseJson(QByteArray bytes)
{
    QJsonParseError err;
    QJsonDocument jsonDoc = QJsonDocument::fromJson(bytes, &err); // 检测json格式
    if (err.error != QJsonParseError::NoError) // Json格式错误
    {
        return;
    }

    QJsonObject jsObj = jsonDoc.object();
    QString message = jsObj.value("message").toString();
    if (message != "Success !")
    {
        QMessageBox::information(this, tr("The information of Json_desc"),
                                 tr("天气:城市错误!"), QMessageBox::Ok );
        city = cityTmp;
        return;
    }

    QString dateStr = jsObj.value("date").toString();
    today.date = QDate::fromString(dateStr, "yyyyMMdd").toString("yyyy-MM-dd");
    today.city = jsObj.value("city").toString();

    // 解析data
    QJsonObject dataObj = jsObj.value("data").toObject();
    today.shidu = dataObj.value("shidu").toString();
    today.pm25 = QString::number( dataObj.value("pm25").toDouble() );
    today.quality = dataObj.value("quality").toString();
    today.wendu = dataObj.value("wendu").toString() + "°";
    today.ganmao = dataObj.value("ganmao").toString();

    // 解析data中的yesterday
    QJsonObject yestObj = dataObj.value("yesterday").toObject();
    forecast[0].date = yestObj.value("date").toString();
    forecast[0].high = yestObj.value("high").toString();
    forecast[0].low = yestObj.value("low").toString();
    forecast[0].aqi = QString::number( yestObj.value("aqi").toDouble() );
    forecast[0].type = yestObj.value("type").toString();

    // 解析data中的forecast
    QJsonArray forecastArr = dataObj.value("forecast").toArray();
    int j = 0;
    for (int i = 1; i < 6; i++)
    {
       QJsonObject dateObj = forecastArr.at(j).toObject();
       forecast[i].date = dateObj.value("date").toString();
       forecast[i].aqi = QString::number( dateObj.value("aqi").toDouble() );
       forecast[i].high = dateObj.value("high").toString();
       forecast[i].low = dateObj.value("low").toString();
       forecast[i].type = dateObj.value("type").toString();
       j++;
    }

    // 取得今日数据
    QJsonObject todayObj = forecastArr.at(0).toObject();
    today.fx = todayObj.value("fx").toString();
    today.fl = todayObj.value("fl").toString();
    today.type = todayObj.value("type").toString();
    today.sunrise = todayObj.value("sunrise").toString();
    today.sunset = todayObj.value("sunset").toString();
    today.notice = todayObj.value("notice").toString();

    setLabelContent();
}

/* 设置控件文本 */
void Weather::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);

    // 判断白天还是夜晚图标
    QString sunsetTime = today.date + " " + today.sunset;
    if (QDateTime::currentDateTime() <= QDateTime::fromString(sunsetTime, "yyyy-MM-dd hh:mm"))
    {
        ui->typeIcoLb->setStyleSheet( tr("border-image: url(:/day/day/%1.png); background-color: argb(60, 60, 60, 0);").arg(today.type) );
    }
    else
    {
        ui->typeIcoLb->setStyleSheet( tr("border-image: url(:/night/night/%1.png); background-color: argb(60, 60, 60, 0);").arg(today.type) );
    }

    ui->qualityLb->setText(today.quality == "" ? "无" : today.quality);
    if (today.quality == "优")
    {
        ui->qualityLb->setStyleSheet("color: rgb(0, 255, 0); background-color: argb(255, 255, 255, 0);");
    }
    else if (today.quality == "良")
    {
         ui->qualityLb->setStyleSheet("color: rgb(255, 255, 0); background-color: argb(255, 255, 255, 0);");
    }
    else if (today.quality == "轻度污染")
    {
        ui->qualityLb->setStyleSheet("color: rgb(255, 170, 0); background-color: argb(255, 255, 255, 0);");
    }
    else if (today.quality == "重度污染")
    {
        ui->qualityLb->setStyleSheet("color: rgb(255, 0, 0); background-color: argb(255, 255, 255, 0);");
    }
    else if (today.quality == "严重污染")
    {
        ui->qualityLb->setStyleSheet("color: rgb(170, 0, 0); background-color: argb(255, 255, 255, 0);");
    }
    else
    {
         ui->qualityLb->setStyleSheet("color: rgb(255, 255, 255); background-color: argb(255, 255, 255, 0);");
    }

    // 六天数据
    for (int i = 0; i < 6; i++)
    {
        forecast_week_list[i]->setText(forecast[i].date.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(tr("优质"));
            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(tr("良好"));
            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(tr("轻度污染"));
            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(tr("重度污染"));
            forecast_aqi_list[i]->setStyleSheet("color: rgb(255, 0, 0);");
        }
        else
        {
            forecast_aqi_list[i]->setText(tr("严重污染"));
            forecast_aqi_list[i]->setStyleSheet("color: rgb(170, 0, 0);");
        }
    }//for

    ui->week0Lb->setText( tr("昨天") );
    ui->week1Lb->setText( tr("今天") );

    ui->curveLb->update();
}

/* 搜索按钮 */
void Weather::on_searchBt_clicked()
{
    cityTmp = city;
    city = ui->cityLineEdit->text();
    getWeatherInfo(manager);
}

/* 刷新按钮 */
void Weather::on_refreshBt_clicked()
{
    getWeatherInfo(manager);
    ui->curveLb->update();
}

/* 事件过滤 */
bool Weather::eventFilter(QObject *watched, QEvent *event)
{
    if (watched == ui->sunRiseSetLb && event->type() == QEvent::Paint)
    {
        paintSunRiseSet();
    }
    else if (watched == ui->curveLb && event->type() == QEvent::Paint)
    {
        paintCurve();
    }
    else if (watched == ui->cityLineEdit && event->type() == QEvent::MouseButtonPress)
    {
        callKeyBoard(); //调用软键盘
    }

    return QWidget::eventFilter(watched,event);
}

/* 日出日落图形绘制 */
void Weather::paintSunRiseSet()
{
    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();

    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, tr("日出日落"));
    painter.restore();

    // 绘制圆弧
    painter.save();
//    pen.setWidth(1);
    pen.setWidthF(0.5);
    pen.setStyle(Qt::DotLine); // 虚线
    pen.setColor(Qt::green);
    painter.setPen(pen);
    painter.drawArc(rect[0], 0 * 16, 180 * 16);
    painter.restore();

    // 绘制日出日落占比
    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;

        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); // 扇形绘制
        }
    }
}

/* 温度曲线绘制 */
void Weather::paintCurve()
{
    QPainter painter(ui->curveLb);
    painter.setRenderHint(QPainter::Antialiasing, true); // 反锯齿

    // 将温度转换为int类型,并计算平均值,平均值作为curveLb曲线的参考值,参考Y坐标为45
    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);

    // 高温曲线绘制
    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]);
    }
}

/* 调用软键盘 */
void Weather::callKeyBoard()
{
//    KeyBoard *keyBoard = new KeyBoard(0, ui->cityLineEdit);
//    keyBoard->show();
}

/* 窗口移动 */
void Weather::mousePressEvent(QMouseEvent *event)
{
    mPos = event->globalPos() - this->pos();
}

void Weather::mouseMoveEvent(QMouseEvent *event)
{
    this->move(event->globalPos() - mPos);
}

/* 右键菜单 */
void Weather::contextMenuEvent(QContextMenuEvent *menuEvent)
{
    m_pMenu->exec(QCursor::pos());
    menuEvent->accept();
}

void Weather::slot_exitApp()
{
    qApp->exit(0);
}
 

 

四 部分说明

 

4.1  界面设计

 

  如图4.1所示,天气窗口界面由图中①、②、③、④四部分。其中:

(1)第①部分包括日期框(QLabel),城市输入框(QLineEdit),搜索按钮和刷新按钮(QToolButton)。

(2)第②部分包括空气质量图标和空气质量框,实时温度显示框(字体大小设置50pt,白色,字体为"Arial"),城市图标和城市显示框(栅格布局),天气类型显示框,天气图标框,天气提醒框。温度显示框样式表设置为边框角度4px,背景色RGB为(60,60,60),透明度为130;其它控件皆为QLabel,透明度为0。

(3)第③部分采用栅格布局,圆角设置为4px,背景RGB为(60,60,60),透明度为130。感冒指数框(QTextBrowser)设置背景透明度为0。右下角为QLabel,用于绘制日出日落曲线。

 

图4.1  天气窗口UI设计

 

(4)第④部分整体采用栅格布局,由43个QLabel控件组成。其中:

①第一行用于显示星期,从左至右依次命令为:week0Lb - week5Lb;

②第二行用于显示日期,从左至右依次命名为:date0Lb - date5Lb;

③第三行用于显示空气质量,从左至右依次命名为:quality0Lb - quality5Lb;

④第四行用于显示天气类型图标,从左至右依次命名为:type0Lb - type5Lb;

⑤第五行用于显示天气类型图标,从左至右依次命名为:typeIco0Lb - typeIco5Lb;

⑥第五行用于显示日最高温度,从左至右依次命名为:high0Lb - high5Lb;

⑦第六行为一整个QLabel,用于绘制日最高、最低温度曲线,命名为:curveLb;

⑧第七行用于显示日最低温度,从左至右依次命名为:low0Lb - low5Lb。

 

4.2  由API获取天气信息

  网络上由很多免费的天气信息API,大致分为两种,一种是获取xml编码格式的天气信息,一种是获取Json编码格式的天气信息。Qt对于Json和xml格式文件都有自己的解析方式,而解析Json更为简单、方便,所以选择解析Json格式的天气信息API。

浏览器输入API,获得的信息如下图(图4.2)所示(以北京为例):

 

图4.2  天气APi获取天气信息示例

注:API来源于http://www.sojson.com/

 

    Qt5提供了QNetworkAccessManager类来获取网络信息,并且有“finished()”信号来响应槽函数,槽函数获取信息并解析。具体步骤如下:

(1)新建QNetworkAccessManager类对象manager,并连接信号与槽函数:

1  manager = new QNetworkAccessManager(this);

2  connect(manager, SIGNAL(finished(QNetworkReply*)),

this, SLOT(replayFinished(QNetworkReply*)));

 

(2)新建QUrl类对象jsonUrl,并且将API和城市传入,请求数据:

1  QUrl jsonUrl(url + city);

2  manager->get( QNetworkRequest(jsonUrl) );

 

(3)请求数据完成后,得到储存有信息的QNetworkReply类对象,读取数据:

1  QVariant status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);

2  if(reply->error() != QNetworkReply::NoError || status_code != 200)

3  {

4    QMessageBox::warning(this, "错误", "天气:请求数据错误,检查网络连接!", QMessageBox::Ok);

5    return;

6  }

7  QByteArray bytes = reply->readAll();

 

 

4.3  解析Json格式天气信息

   如图4.2所示,Json编码格式数据[6]以“{}”包含,Qt把这整体数据叫做“JsonDocument”,相当于目录,并且提供了QJsonDocument类来将信息转换为Qt能够解析的Json数据格式,和QJsonParseError类来检测数据是不是Json格式。

1  QJsonParseError err;

2  QJsonDocument jsonDoc = QJsonDocument::fromJson(bytes, &err); // 检测json格式

3  if (err.error != QJsonParseError::NoError) // Json格式错误

4  {

5    return;

6  }

 

   得到Json目录后,里面的内容叫做“QJsonObject”,可以由QJsonDocument的“object()”方法来获取:

1  QJsonObject jsObj = jsonDoc.object();

 

          图4.3  Json格式数据片段

 

   如图4.3所示Json数据片段“”date”:”20180102””,其中”date”叫做“键”,”20180102”叫做键对应的值,取得Json数据的“键”来获取对应的“值”,Qt将之叫做“value”。

   依次取得日期(date)、消息(message)、城市(city)等能一步取得的信息。键“data”对应的值又是一个“QJsonObject”,取得“data”键值,再将之转换为Object:

1  QJsonObject dataObj = jsObj.value("data").toObject();

   接着,依次取得dataObj包含的键值:

1    today.shidu = dataObj.value("shidu").toString();

2    today.pm25 = QString::number( dataObj.value("pm25").toDouble() );

3    today.quality = dataObj.value("quality").toString();

4    today.wendu = dataObj.value("wendu").toString() + "°";

5    today.ganmao = dataObj.value("ganmao").toString();

再看键“forecast”对应的值为数组,Qt将Json数组叫做“QJsonArray”,取得Json数组:

1    QJsonArray forecastArr = dataObj.value("forecast").toArray();

 

  每个数组元素对应一个“QJsonObject”,依照上述方法,依次取得里面的键值即可。Qt对Json格式数据的解析以及转换如表4.1、表4.2、图4.4所示:

 

 

表4.1 Qt对Json格式数据的解析示例
数据 操作
源数据 QJsonDocument jsonDoc = QJsonDocument::fromJson(bytes, &err);
Json整体数据 QJsonObject jsObj = jsonDoc.object();
普通“值(value)” QString message = jsObj.value("message").toString();
带对象(Object)的“值” QJsonObject dataObj = jsObj.value("data").toObject();
带数组(Array)的“值” QJsonArray forecastArr = dataObj.value("forecast").toArray();
数组元素为对象 QJsonObject dateObj = forecastArr.at(j).toObject();
表4.2 数据间的相互转换关系表
数据类型 转换方法 转换后数据类型
QByteArray QJsonDocument::fromJson(QByteArray, QJsonParseError) QJsonDocument
QJsonDocument object() QJsonObject
QJsonObject value(QString key) QJsonValue
QJsonValue toObject() QJsonObject
QJsonValue toArray() QJsonArray
QJsonValue toString() QString
QJsonValue toDouble() double
double QString::number(double) QString
QString toDouble() double
 

 

图4.4  数据间的相互转换关系图

 

为了方便管理和赋值,我们将今日天气数据和六天天气信息设计成结构体,如下:

1  struct Today

2  {

3     QString date;

4     QString wendu;

5     QString city;

6     QString shidu;

7     QString pm25;

8     QString quality;

9     QString ganmao;

10    QString fx;

11    QString fl;

12    QString type;

13    QString sunrise;

14    QString sunset;

15    QString notice;

16  };

 

1  struct Forecast

2  {

3     QString date;

4     QString high;

5     QString low;

6     QString aqi;

7     QString type;

8  };

 

   定义Forecast结构体数组forecast,元素数量为6,将界面④部分的控件使用容器来管理,再使用for循环依次赋值。容器定义如下:

1    QList<QLabel *> forecast_week_list;

2    QList<QLabel *> forecast_date_list;

3    QList<QLabel *> forecast_aqi_list;

4    QList<QLabel *> forecast_type_list;

5    QList<QLabel *> forecast_typeIco_list;

6    QList<QLabel *> forecast_high_list;

7    QList<QLabel *> forecast_low_list;

 

4.4  绘制日出日落图案

 

4.4.1  Qt5事件过滤器简介

    QLabel本身是有绘图事件(QPaintevent)的,然而,QWidget上的QLabel控件的绘图事件会被QWidget屏蔽,所以我们不能直接将其作为QPaintDevice来绘图。这时,需要用到事件过滤机制。

   Qt提供了事件过滤器来实现在一个部件中监控其他多个部件的事件。事件过滤器与其他部件不同,它不是一个类,只是由两个函数组成的一种操作,用来完成一个部件对其他部件事件的监视。这两个函数分别是installEventFilter()和eventFilter(),都是QObject类中的函数。

   那么,首先,给日出日落绘图控件(sunRiseSetLb)启用事件过滤器:

1    ui->sunRiseSetLb->installEventFilter(this);

 

   然后,才能在eventFilter函数中进行绘图操作。

 

4.4.2  图案绘制

(一)绘制底线和文本

  将画笔宽度设置为0.5,颜色设置为黄色(Qt::yellow),使用Qpainter类的drawLine()方法绘制底线,起始点坐标为:(20,75),终止点坐标为:(130,75)。

  再将画笔字体设置为微软雅黑(Microsoft Yahei),文字大小设置为8pt,粗细设置为正常(QFont::Normal),颜色设置为白色。使用QPainter类的drawText()方法绘制文本,文本内容为日出、日落时间和“日出日落”四个字。日出时间文本绘制区域为:(0,80,50,20);日落时间绘制区域为:(100, 80, 50, 20);“日出日落”文字绘制区域为:(50, 80, 50, 20)。绘制效果如下图(图4.5)所示:

 

 

图4.5  底线和文本绘制

(二)绘制圆弧

  为了达到生动形象的效果,我们以半圆来表示日出日落,半圆底线已经绘制完毕,接下来绘制弧线,弧线采用虚线方式来表现。QPainter的画笔设置虚线的方法为QPen类的setStyle()函数,画笔的各种风格如图4.6所示。

 

图4.6  画笔的各种风格

 

  选择Qt::DotLine风格,画笔颜色设置为绿色(Qt::green),宽度设置为0.5,调用QPainter类的drawArc()函数来进行圆弧的绘制。Qt5帮助手册对于此函数的描述为:

void QPainter::drawArc(const QRectF &rectangle, int startAngle, int spanAngle)

Draws the arc defined by the given rectangle, startAngle and spanAngle.

The startAngle and spanAngle must be specified in 1/16th of a degree, i.e. a full circle equals 5760 (16 * 360). Positive values for the angles mean counter-clockwise while negative values mean the clockwise direction. Zero degrees is at the 3 o'clock position.

 

  可知,第一个参数指定一个矩形(QRect),画笔会自动将圆心设置在矩形中心。第二个参数为起始角度,第三个参数为跨越角度,跨越方向为逆时针方向。需要注意的是,Qt对于绘制圆弧的角度是正常角度的1/16,也就是说,起始角度和跨越角度都需要扩大16倍。

  那么,我们指定矩形区域为:(25, 25, 100, 100),起始角度为0,终止角度为180×16,绘制出来的效果如图4.7所示。

 

图4.7  圆弧绘制

(三)绘制日出日落占比图形

  日出日落图形以扇形方式展现,从0到180度。那么需要计算当前时间对于日出日落时间的占比是多少,然后根据占比来计算起始角度和跨越角度。步骤如下:

(1)计算出日出日落时间和当前时间,忽略秒,用分钟单位来表示:

1  static QString sunsetHour = sunSetTime.at(0);

2  static QString sunsetMint = sunSetTime.at(1);

3  static QString sunriseHour = sunRiseTime.at(0);

4  static QString sunriseMint = sunRiseTime.at(1);

5  static int sunrise = sunriseHour.toInt() * 60 + sunriseMint.toInt();

6  static int sunset = sunsetHour.toInt() * 60 + sunsetMint.toInt();

7  int now = QTime::currentTime().hour() * 60 + QTime::currentTime().minute();

 

(2)那么,起始角度为:

1  startAngle = ( (double)(sunset - now) / (sunset - sunrise) ) * 180 * 16;

 

(3)跨越角度为:

2  spanAngle = ( (double)(now - sunrise) / (sunset - sunrise) ) * 180 * 16;

 

(4)绘制扇形:

1  painter.drawPie(rect[0], startAngle, spanAngle);

 

  完成绘制函数后,设定一个定时器,以1秒间隔重新绘制即可。

 

4.5  绘制日最高、最低温曲线

  同样的,曲线是在QLabel控件上绘制,同样需要事件过滤器:

1  ui->curveLb->installEventFilter(this);

 

(一)曲线转折点X坐标计算及扩张值设定

  为了防止温度过高或过低导致曲线超出控件范围,也为了使温度曲线能够大致居中显示,防止曲线曲线过于偏上或者偏下,需要计算出六天最高温度的平均值。curveLb控件高度为105,所以给定最高温度平均值起始Y坐标为45较为合适。

  为了界面的美观和曲线的平衡,将curveLb平均分成六份,每一份的中心点作为温度曲线的转折点,六个中心点的x坐标如下:

1  int pointX[6] = {35, 103, 172, 241, 310, 379};

 

  然后是温度每升高一度,y坐标的扩张值设定。如果扩张值设定过大,那么日温差就必须小;如果扩张值设定过小,那么最高最低温曲线倾斜度不够,不能够明显表现温差。经测试,扩张值设定为3正好合适。用s来表示所能容纳的日温差,单位为度(°),那么有:

                     s = [(105-45) ÷ 3]° ≈ 22°            (4.1)

  这个值已经能满足绝大多数城市的日温差。

 

(二)曲线转折点Y坐标的计算

  假定高温平均值为a,日最高温度为h,日最低温度为l,起始点坐标为s,扩张值为i,日最高温Y坐标为HY,日最低温Y坐标为LY。那么有:

                      HY = s - (h-a) × i             (4.2)

                      LY = s + (a-l) × i              (4.3)

(三)曲线绘制

  取得每个转折点的X坐标和Y坐标后,即可使用QPainter类的drawLine()函数,依坐标点一段一段绘线。由于六天天气信息中,包含了昨天的天气信息,为了使曲线更为明显地突出昨天温度与未来五天的温度,故绘制曲线时将第一段线绘制为虚线(Qt::DotLine)。为了凸出显示出日最高温和日最低温度,绘制最高温度曲线时,画笔颜色设置为RGB(255, 170, 0);绘制最低温度曲线时,画笔颜色设置为RGB(0, 255, 255)。

(1)绘制高温曲线核心代码:

1  painter.save();

2  pen.setColor(QColor(255, 170, 0));

3  pen.setStyle(Qt::DotLine);

4  painter.setPen(pen);

5  painter.setBrush(QColor(255, 170, 0));

6  painter.drawEllipse(QPoint(pointX[0], pointHY[0]), ORIGIN_SIZE, ORIGIN_SIZE);

7  painter.drawEllipse(QPoint(pointX[1], pointHY[1]), ORIGIN_SIZE, ORIGIN_SIZE);

8  painter.drawLine(pointX[0], pointHY[0], pointX[1], pointHY[1]);

9  pen.setStyle(Qt::SolidLine);

10  pen.setWidth(1);

11  painter.setPen(pen);

12  for (int i = 1; i < 5; i++)

13  {

14    painter.drawEllipse(QPoint(pointX[i+1], pointHY[i+1]), ORIGIN_SIZE, ORIGIN_SIZE);

15    painter.drawLine(pointX[i], pointHY[i], pointX[i+1], pointHY[i+1]);

16  }

17  painter.restore();

 

(1)绘制低温曲线核心代码:

1  pen.setColor(QColor(0, 255, 255));

2  pen.setStyle(Qt::DotLine);

3  painter.setPen(pen);

4  painter.setBrush(QColor(0, 255, 255));

5  painter.drawEllipse(QPoint(pointX[0], pointLY[0]), ORIGIN_SIZE, ORIGIN_SIZE);

6  painter.drawEllipse(QPoint(pointX[1], pointLY[1]), ORIGIN_SIZE, ORIGIN_SIZE);

7  painter.drawLine(pointX[0], pointLY[0], pointX[1], pointLY[1]);

8  pen.setColor(QColor(0, 255, 255));

9  pen.setStyle(Qt::SolidLine);

10  painter.setPen(pen);

11  for (int i = 1; i < 5; i++)

12  {

13    painter.drawEllipse(QPoint(pointX[i+1], pointLY[i+1]), ORIGIN_SIZE, ORIGIN_SIZE);

14    painter.drawLine(pointX[i], pointLY[i], pointX[i+1], pointLY[i+1]);

15  }

 

(2)效果如下图(图4.8)所示:

 

图4.8  温度曲线效果图

 

五 代码下载地址

http://download.csdn.net/download/wu9797/10213010

 

展开阅读全文

没有更多推荐了,返回首页