Building a Qt Weather Forecast Interface with HTTP Data Fetching and JSON Parsing
Build a weather forecast interface that retrieves and displays meteorological data for various cities, encompassing Qt Stylesheet-based UI customization, HTTP network communication, JSON data parsing, custom temperature curve visualization, and end-to-end integration and debugging of multiple components.
UI Design and Qt Stylesheet Customization
Layout Structure
Organize the interface into functional upper and lower sections with consistent widget naming conventions.
Stylesheet Customization Techniques
Border Radius Settings
border-radius: 4px;
Direction-Specific Border Radius
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
Background and Font Styling
background-color: rgba(60, 60, 60, 100);
color: rgb(230, 230, 230);
font: 45pt "Segoe UI";
Multi-State Button Styling
QPushButton#btnRefresh {
background-color: rgba(220, 250, 220, 255);
border: 10px solid white;
border-radius: 32px;
font-size: 60px;
}
QPushButton#btnRefresh:hover {
background-color: rgba(255, 255, 0, 255);
border: 10px solid white;
border-radius: 32px;
color: #FF00FE;
font-size: 60px;
}
QPushButton#btnRefresh:pressed {
background-color: rgba(255, 180, 0, 255);
border: 10px solid white;
border-radius: 32px;
color: #FF00FE;
font-size: 60px;
}
Inheritance-Based Styling
Parent widgets can define styles for child widgets, which apply unless overridden:
QLabel {
background-color: rgba(0, 200, 200, 200);
border-radius: 4px;
}
Weather Data APIs
Use public JSON-based weather APIs to fetch data. Validate responses with online tools like JSON Formatter & Validator.
Available APIs
- City-specific weather endpoint:
http://t.weather.itboy.net/api/weather/city/101010100 - Yiketianqi API:
- Current location weather:
http://v1.yiketianqi.com/api?unescape=1&version=v61&appid=89361488&appsecret=K6tTmCT0 - Target city daily weather:
http://v1.yiketianqi.com/api?unescape=1&version=v61&appid=89361488&appsecret=K6tTmCT0&cityid=<CITY_ID> - 7-day forecast for target city:
http://v1.yiketianqi.com/api?unescape=1&version=v9&appid=89361488&appsecret=K6tTmCT0&cityid=<CITY_ID>
- Current location weather:
JSON Data Handling in Qt
Overview of JSON
JSON (JavaScript Object Notation) is a lightweight data interchange format using key-value pairs, ideal for network communication due to its readability, compact size, cross-language suport, and ease of parsing. It is widely used in web APIs and cross-system data exchange.
Generating JSON Data
Create and save structured JSON data using Qt's QJsonDocument, QJsonObject, and QJsonArray classes:
Sample JSON Output:
{
"cityId": 1010200,
"date": "2024-03-15",
"dataEntries": [
"forecast1",
"forecast2",
200
],
"weatherCondition": "Snowy"
}
Qt Implementation:
void generateWeatherSampleJson() {
QJsonObject mainJsonObject;
mainJsonObject["cityId"] = 1010200;
mainJsonObject["date"] = "2024-03-15";
mainJsonObject["weatherCondition"] = "Snowy";
QJsonArray dataEntries;
dataEntries.append("forecast1");
dataEntries.append("forecast2");
dataEntries.append(200);
mainJsonObject["dataEntries"] = dataEntries;
QJsonObject alarmObj;
alarmObj["alarmType"] = "Snow Warning";
alarmObj["alarmLevel"] = "Orange";
alarmObj["alarmContent"] = "Shanghai issues orange snow warning";
mainJsonObject["weatherAlarm"] = alarmObj;
QJsonArray dailyForecastArray;
QJsonObject day1;
day1["weekday"] = "Friday";
day1["date"] = "2024-03-15";
day1["weather"] = "Snowy";
day1["temperature"] = 5;
dailyForecastArray.append(day1);
QJsonObject day2;
day2["weekday"] = "Saturday";
day2["date"] = "2024-03-16";
day2["weather"] = "Cloudy";
day2["temperature"] = 8;
dailyForecastArray.append(day2);
mainJsonObject["dailyForecast"] = dailyForecastArray;
QJsonDocument jsonDoc(mainJsonObject);
QByteArray jsonBytes = jsonDoc.toJson(QJsonDocument::Indented);
QFile outputFile("C:/temp/weather_sample.json");
if (!outputFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << "Failed to open file for writing";
return;
}
outputFile.write(jsonBytes);
outputFile.close();
}
Parsing JSON Data
Parse JSON content from a file or network response using Qt's JSON classes:
void loadAndParseJsonData() {
QFile inputFile("C:/temp/weather_sample.json");
if (!inputFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "Failed to open JSON file";
return;
}
QString jsonContent = inputFile.readAll();
inputFile.close();
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonContent.toUtf8());
if (jsonDoc.isNull() || !jsonDoc.isObject()) {
qDebug() << "Invalid JSON data";
return;
}
QJsonObject weatherDataObj = jsonDoc.object();
qDebug() << "--- Root Object Data ---";
int cityId = weatherDataObj["cityId"].toInt();
QString date = weatherDataObj["date"].toString();
QString weather = weatherDataObj["weatherCondition"].toString();
qDebug() << "City ID:" << cityId;
qDebug() << "Date:" << date;
qDebug() << "Weather:" << weather;
if (weatherDataObj.contains("dataEntries") && weatherDataObj["dataEntries"].isArray()) {
QJsonArray dataArray = weatherDataObj["dataEntries"].toArray();
qDebug() << "--- Array Data ---";
int index = 0;
for (const QJsonValue& val : dataArray) {
switch (val.type()) {
case QJsonValue::String:
qDebug() << "Entry" << index << ":" << val.toString();
break;
case QJsonValue::Double:
qDebug() << "Entry" << index << ":" << val.toInt();
break;
default:
qDebug() << "Entry" << index << ": Unsupported type";
break;
}
index++;
}
}
if (weatherDataObj.contains("weatherAlarm") && weatherDataObj["weatherAlarm"].isObject()) {
QJsonObject alarmData = weatherDataObj["weatherAlarm"].toObject();
qDebug() << "--- Weather Alarm ---";
qDebug() << "Type:" << alarmData["alarmType"].toString();
qDebug() << "Level:" << alarmData["alarmLevel"].toString();
qDebug() << "Content:" << alarmData["alarmContent"].toString();
}
}
Parsing JSON to QMap
Convert simple JSON key-value pairs to a QMap for easy access:
void parseJsonToQMap() {
QString jsonString = R"(
{
"userName": "Jane Smith",
"age": "28",
"contact": "jane.smith@example.com"
}
)";
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonString.toUtf8());
QMap<QString, QString> userDataMap;
if (!jsonDoc.isNull() && jsonDoc.isObject()) {
QJsonObject userObj = jsonDoc.object();
for (const QString& key : userObj.keys()) {
userDataMap[key] = userObj[key].toString();
}
} else {
qDebug() << "Invalid JSON input";
return;
}
qDebug() << "--- User Data Map ---";
for (const QString& key : userDataMap.keys()) {
qDebug() << key << ":" << userDataMap[key];
}
}
Network Architecture and HTTP Basics
Client-Server (CS) vs Browser-Server (BS) Architectures
- CS Architecture: Two-tier system with dedicated client app and server. Clients handle UI and local processing, servers manage data and business logic. Requires platform-specific client development, offers better performance and rich UI but higher maintenance cost.
- BS Architecture: Web-based system using standard browsers as clients. All business logic runs on servers, enabling cross-platform access with minimal client setup. Lower maintenance cost but limited by web technology capabilities and network performance.
Core HTTP Concepts
HTTP (Hypertext Transfer Protocol) is the foundation of web communication, using request-response model:
- Request Methods: GET (retrieve resources), POST (submit data), PUT (update resources), DELETE (remove resources), HEAD (get metadata).
- Status Codes: 200 OK (success), 404 Not Found (resource missing), 500 Internal Server Error (server failure), 301 Moved Permanently (resource relocated).
- Headers: Metadata like
Content-Type(response format),User-Agent(client info). - Statelessness: No built-in session tracking; cookies or tokens are used to maintain state between requests.
- HTTPS: Secure HTTP with SSL/TLS encryption for data confidentiality.
Qt HTTP Programming
Use Qt's QNetworkAccessManager, QNetworkRequest, and QNetworkReply for asynchronous HTTP communication:
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QDebug>
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
QNetworkAccessManager networkManager;
QNetworkRequest request(QUrl("https://jsonplaceholder.typicode.com/todos/1"));
QNetworkReply* httpReply = networkManager.get(request);
QObject::connect(httpReply, &QNetworkReply::finished, [&]() {
if (httpReply->error() != QNetworkReply::NoError) {
qDebug() << "Request error:" << httpReply->errorString();
httpReply->deleteLater();
return;
}
QString responseData = httpReply->readAll();
qDebug() << "Response received:" << responseData;
httpReply->deleteLater();
});
return app.exec();
}
Weather Forecast Project Integration
Fetch, Parse, and Refresh UI
Use QNetworkAccessManager to fetch weather data, parse JSON resposne, and update UI components:
- Initialize Network Request:
// In widget constructor
QNetworkAccessManager* networkManager = new QNetworkAccessManager(this);
QString baseApiUrl = "http://v1.yiketianqi.com/api?unescape=1&version=v9&appid=89361488&appsecret=K6tTmCT0";
QNetworkRequest apiRequest(QUrl(baseApiUrl));
networkManager->get(apiRequest);
connect(networkManager, &QNetworkAccessManager::finished, this, &WeatherWidget::processWeatherApiResponse);
- Process API Response:
class WeatherDay {
public:
QString cityName;
QString date;
QString weekday;
QString currentTemp;
QString weatherType;
QString lowTemp;
QString highTemp;
QString weatherTips;
QString windDirection;
QString windSpeed;
QString pm25;
QString humidity;
QString airQuality;
};
WeatherDay weeklyWeatherData[7];
void WeatherWidget::processWeatherApiResponse(QNetworkReply* reply) {
int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (reply->error() != QNetworkReply::NoError || responseCode != 200) {
qDebug() << "Network error:" << reply->errorString();
QMessageBox::critical(this, "Error", "Failed to fetch weather data.");
reply->deleteLater();
return;
}
QByteArray responseBytes = reply->readAll();
parseWeatherJsonResponse(responseBytes);
reply->deleteLater();
}
- Parse JSON to WeatherDay Objects:
void WeatherWidget::parseWeatherJsonResponse(QByteArray jsonBytes) {
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonBytes);
if (jsonDoc.isNull() || !jsonDoc.isObject()) {
qDebug() << "Invalid weather JSON data";
return;
}
QJsonObject rootObj = jsonDoc.object();
weeklyWeatherData[0].cityName = rootObj["city"].toString();
weeklyWeatherData[0].pm25 = rootObj["aqi"].toObject()["pm25"].toString();
if (rootObj.contains("data") && rootObj["data"].isArray()) {
QJsonArray forecastArray = rootObj["data"].toArray();
for (int i = 0; i < qMin(7, (int)forecastArray.size()); i++) {
QJsonObject dayObj = forecastArray[i].toObject();
weeklyWeatherData[i].weekday = dayObj["week"].toString();
weeklyWeatherData[i].date = dayObj["date"].toString();
weeklyWeatherData[i].weatherType = dayObj["wea"].toString();
weeklyWeatherData[i].airQuality = dayObj["air_level"].toString();
weeklyWeatherData[i].windDirection = dayObj["win"].toArray()[0].toString();
weeklyWeatherData[i].windSpeed = dayObj["win_speed"].toString();
weeklyWeatherData[i].currentTemp = dayObj["tem"].toString();
weeklyWeatherData[i].lowTemp = dayObj["tem2"].toString();
weeklyWeatherData[i].highTemp = dayObj["tem1"].toString();
weeklyWeatherData[i].weatherTips = dayObj["index"].toArray()[3].toObject()["desc"].toString();
weeklyWeatherData[i].humidity = dayObj["humidity"].toString();
}
}
refreshWeatherDisplay();
}
- Update UI Components:
void WeatherWidget::refreshWeatherDisplay() {
ui->lblCurrentDate->setText(weeklyWeatherData[0].date + " " + weeklyWeatherData[0].weekday);
ui->lblCityName->setText(weeklyWeatherData[0].cityName);
ui->lblCurrentTemp->setText(weeklyWeatherData[0].currentTemp + "℃");
ui->lblTempRange->setText(weeklyWeatherData[0].lowTemp + "℃ - " + weeklyWeatherData[0].highTemp + "℃");
ui->lblWeatherType->setText(weeklyWeatherData[0].weatherType);
ui->lblWeatherIcon->setPixmap(getWeatherIcon(weeklyWeatherData[0].weatherType));
ui->lblWeatherTips->setText("Tips: " + weeklyWeatherData[0].weatherTips);
ui->lblWindDir->setText(weeklyWeatherData[0].windDirection);
ui->lblWindSpeed->setText(weeklyWeatherData[0].windSpeed);
ui->lblPM25->setText(weeklyWeatherData[0].pm25);
ui->lblHumidity->setText(weeklyWeatherData[0].humidity);
ui->lblAirQuality->setText(weeklyWeatherData[0].airQuality);
QStringList weekdayLabels = {"Today", "Tomorrow", "Day After", "4th Day", "5th Day", "6th Day", "7th Day"};
for (int i = 0; i < 7; i++) {
ui->lblWeekday[i]->setText(weekdayLabels[i]);
QStringList dateParts = weeklyWeatherData[i].date.split("-");
ui->lblShortDate[i]->setText(dateParts[1] + "-" + dateParts[2]);
ui->lblDayWeatherType[i]->setText(weeklyWeatherData[i].weatherType);
ui->lblDayWeatherIcon[i]->setPixmap(getWeatherIcon(weeklyWeatherData[i].weatherType));
QString airQuality = weeklyWeatherData[i].airQuality;
QString styleSheet = "border-radius: 8px; color: rgb(230,230,230); ";
if (airQuality == "Excellent") styleSheet += "background-color: rgb(124,198,55);";
else if (airQuality == "Good") styleSheet += "background-color: rgb(208,107,39);";
else if (airQuality == "Light Pollution") styleSheet += "background-color: rgb(255,200,200);";
else if (airQuality == "Moderate Pollution") styleSheet += "background-color: rgb(255,17,17);";
else if (airQuality == "Severe Pollution") styleSheet += "background-color: rgb(153,0,0);";
ui->lblAirQualityBadge[i]->setStyleSheet(styleSheet);
ui->lblAirQualityBadge[i]->setText(airQuality);
ui->lblDayWindDir[i]->setText(weeklyWeatherData[i].windDirection);
ui->lblDayWindSpeed[i]->setText(weeklyWeatherData[i].windSpeed.split(" turn ").first());
}
update();
}
City Search Functionality
Implement city lookup by name using a preloaded city code JSON file:
- Load City Codes into QMap:
class CityCodeLookup {
private:
QMap<QString, QString> cityCodeMap;
void loadCityCodeData() {
QFile cityFile("C:/temp/city_codes.json");
if (!cityFile.open(QIODevice::ReadOnly)) {
qDebug() << "Failed to load city codes file";
return;
}
QString cityJson = cityFile.readAll();
cityFile.close();
QJsonDocument cityDoc = QJsonDocument::fromJson(cityJson.toUtf8());
if (cityDoc.isArray()) {
QJsonArray cityArray = cityDoc.array();
for (const QJsonValue& cityVal : cityArray) {
if (cityVal.isObject()) {
QJsonObject cityObj = cityVal.toObject();
QString cityName = cityObj["city_name"].toString();
QString cityId = cityObj["city_code"].toString();
cityCodeMap[cityName] = cityId;
}
}
}
}
public:
QString findCityIdByName(QString cityName) {
if (cityCodeMap.isEmpty()) loadCityCodeData();
if (cityCodeMap.contains(cityName)) return cityCodeMap[cityName];
if (cityCodeMap.contains(cityName + " City")) return cityCodeMap[cityName + " City"];
if (cityCodeMap.contains(cityName + " District")) return cityCodeMap[cityName + " District"];
if (cityCodeMap.contains(cityName + " County")) return cityCodeMap[cityName + " County"];
return "";
}
};
- Search Button Click Handler:
void WeatherWidget::on_btnSearch_clicked() {
QString cityName = ui->txtCitySearch->text().trimmed();
if (cityName.isEmpty()) return;
CityCodeLookup cityLookup;
QString cityId = cityLookup.findCityIdByName(cityName);
if (cityId.isEmpty()) {
QMessageBox::warning(this, "Warning", "Invalid city name. Please enter a valid city.");
return;
}
QString apiUrl = "http://v1.yiketianqi.com/api?unescape=1&version=v9&appid=89361488&appsecret=K6tTmCT0&cityid=" + cityId;
networkManager->get(QNetworkRequest(QUrl(apiUrl)));
}
Custom Temperature Curve Drawing
Implement custom temperature curve visualization using Qt's event filter and QPainter:
- Instal Event Filter:
WeatherWidget::WeatherWidget(QWidget *parent) : QWidget(parent), ui(new Ui::WeatherWidget) {
ui->setupUi(this);
ui->highTempCanvas->installEventFilter(this);
ui->lowTempCanvas->installEventFilter(this);
}
- Event Filter Implementation:
bool WeatherWidget::eventFilter(QObject* watched, QEvent* event) {
if (watched == ui->highTempCanvas && event->type() == QEvent::Paint) {
drawHighTemperatureCurve();
return true;
}
if (watched == ui->lowTempCanvas && event->type() == QEvent::Paint) {
drawLowTemperatureCurve();
return true;
}
return QWidget::eventFilter(watched, event);
}
- Draw High Temperature Curve:
void WeatherWidget::drawHighTemperatureCurve() {
QPainter painter(ui->highTempCanvas);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QColor(255, 100, 50));
painter.setBrush(QColor(255, 100, 50));
int totalHigh = 0;
for (int i = 0; i < 7; i++) {
totalHigh += weeklyWeatherData[i].highTemp.toInt();
}
float avgHigh = totalHigh / 7.0f;
QPoint tempPoints[7];
for (int i = 0; i < 7; i++) {
int xPos = ui->lblAirQualityBadge[i]->x() + ui->lblAirQualityBadge[i]->width() / 2;
int tempVal = weeklyWeatherData[i].highTemp.toInt();
int yPos = ui->highTempCanvas->height() / 2 - (tempVal - avgHigh) * 4;
tempPoints[i] = QPoint(xPos, yPos);
painter.drawText(QPoint(xPos - 20, yPos - 15), weeklyWeatherData[i].highTemp + "℃");
painter.drawEllipse(tempPoints[i], 4, 4);
}
for (int i = 0; i < 6; i++) {
painter.drawLine(tempPoints[i], tempPoints[i+1]);
}
}
- Draw Low Temperature Curve:
void WeatherWidget::drawLowTemperatureCurve() {
QPainter painter(ui->lowTempCanvas);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QColor(50, 150, 255));
painter.setBrush(QColor(50, 150, 255));
int totalLow = 0;
for (int i = 0; i < 7; i++) {
totalLow += weeklyWeatherData[i].lowTemp.toInt();
}
float avgLow = totalLow / 7.0f;
QPoint tempPoints[7];
for (int i = 0; i < 7; i++) {
int xPos = ui->lblAirQualityBadge[i]->x() + ui->lblAirQualityBadge[i]->width() / 2;
int tempVal = weeklyWeatherData[i].lowTemp.toInt();
int yPos = ui->lowTempCanvas->height() / 2 - (tempVal - avgLow) * 4;
tempPoints[i] = QPoint(xPos, yPos);
painter.drawText(QPoint(xPos - 20, yPos - 15), weeklyWeatherData[i].lowTemp + "℃");
painter.drawEllipse(tempPoints[i], 4, 4);
}
for (int i = 0; i < 6; i++) {
painter.drawLine(tempPoints[i], tempPoints[i+1]);
}
}