本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:用Qt 5/6开发的轻量级桌面天气应用,能联网查询实时天气数据,界面基于mainwindow.ui设计,启动和关闭时有平滑动画效果(通过enterexitanimation.h/cpp实现),图标、背景图等静态资源统一打包在Resource目录下,通过resource.qrc集中管理。天气数据解析逻辑封装在weather.cpp和weater.h中,主程序入口为main.cpp,项目使用标准Qt Creator工程结构,直接打开QOnlineWeather.pro即可编译运行。配套README.txt说明基础操作,.gitignore和Makefile支持跨平台构建,适合想练手Qt界面开发、QNetworkAccessManager网络请求、资源编译和基础动画实现的学习者。所有代码模块职责明确,比如mainwindow负责UI交互,weather类处理API响应与数据映射,动画组件独立解耦,方便替换或扩展功能,比如接入不同天气API、增加城市搜索、温度单位切换等。

1. 项目概述:一个“会呼吸”的桌面天气工具,为什么它值得你花30分钟看懂

我第一次在同事电脑右下角看到这个小窗口时,以为是系统自带的小组件——半透明磨砂背景上,云朵图标缓缓飘过,温度数字像被微风托着一样浮起,点击关闭时整个窗口不是生硬消失,而是像合上一本书那样向中心收拢、淡出。他笑着说:“自己写的,就当练手。”后来我扒开源码才发现,这根本不是“简单练手”,而是一份把Qt桌面开发里最常踩的坑、最易忽略的细节、最容易写成一锅粥的模块耦合问题,全都用教科书级方式拆解清楚的实战样本。它叫QOnlineWeather,核心就三件事:联网拿数据、界面做呈现、动效增体验——但每件事背后,都藏着Qt初学者真正卡壳的地方:比如为什么QNetworkAccessManager发请求后信号连不上槽?为什么resource.qrc改了图标却没更新?为什么动画一加,窗口就卡死或闪退?这些在官方文档里一笔带过的“注意事项”,恰恰是项目能跑起来和跑得稳之间的全部距离。

这个工具用的是标准Qt 5.15+(也兼容Qt 6.2+),不依赖任何第三方UI库,纯原生QWidget实现。它没有炫技式的3D渲染,也没有复杂的城市管理后台,但它把Qt桌面应用开发中最核心的四根支柱全立住了:UI构建(.ui文件+代码联动)、网络通信(QNetworkAccessManager异步处理)、资源管理(.qrc编译进二进制)、动效控制(QPropertyAnimation+状态机)。更关键的是,每个模块都严格遵循“单一职责”——mainwindow.cpp只管按钮点了怎么反应、天气数据来了怎么填进控件;weather.cpp只管从JSON里抠出温度、湿度、天气描述,再转成Qt能用的对象;enterexitanimation.cpp干脆就是一个独立类,不碰UI也不碰网络,只负责“怎么动”。这种结构不是为了好看,而是为了让你改一行代码就能换掉整个天气API,删两个函数就能禁用所有动画,甚至把背景图换成自己拍的照片——都不用动主逻辑。它适合谁?如果你正在Qt Creator里拖完按钮却不知道怎么让它们响应点击;如果你写了QNetworkReply*但永远收不到finished()信号;如果你改了resource.qrc里的路径,编译完还是旧图标……那这个项目就是为你写的。它不教你“Qt是什么”,它直接带你走一遍“Qt该怎么用”。

2. 整体架构与设计思路:为什么这样组织代码,而不是堆在一个main.cpp里

2.1 模块划分的底层逻辑:解耦不是为了炫技,是为了“改得动”

很多新手写Qt程序,习惯性把所有东西塞进mainwindow.cpp:网络请求写在构造函数里,解析JSON写在槽函数里,连动画都用QTimer硬生生刷位置。结果就是改个天气API地址要翻100行代码,想加个“刷新按钮”发现信号根本连不到网络模块。QOnlineWeather的目录结构看似普通,实则每一层都在解决一个具体痛点:

  • mainwindow.{h/cpp/ui}:纯粹的“界面容器”。它不关心天气数据从哪来,只定义“这里放温度数字”“那里放天气图标”“点击这个按钮触发刷新”。所有UI控件(QLabel、QPushButton)都在mainwindow.ui里拖出来,C++代码只做两件事:初始化控件样式(比如设置字体大小、透明度)、连接信号与槽(比如connect(ui->refreshBtn, &QPushButton::clicked, this, &MainWindow::onRefreshClicked))。这样做的好处是,你想把界面从“卡片式”改成“仪表盘式”,只需重画mainwindow.ui,C++逻辑几乎不用动。

  • weather.{h/cpp}:纯粹的“数据翻译官”。它不碰网络、不碰界面,只接收原始JSON字符串(比如{“main”:{“temp”:23.5}}),然后提供getTemperature()、getWeatherIconCode()这样的干净接口。内部用QJsonDocument解析,用枚举(WeatherCondition::Cloudy)映射天气类型,避免在UI层写一堆if (json[“weather”][0][“main”] == “Rain”)。这种封装让数据变更成本降到最低——如果某天API返回格式变了,你只改weather.cpp里的parseData()函数,mainwindow里调用getTemperature()的地方完全不受影响。

  • enterexitanimation.{h/cpp}:纯粹的“动效执行者”。它继承自QObject,内部只持有一个QPropertyAnimation指针和一个目标QWidget指针。它不决定“什么时候动”,只响应startEnterAnimation()或startExitAnimation()指令,按预设参数(持续时间、缓动曲线)执行动画。关键点在于:它和mainwindow之间是“弱关联”——mainwindow在需要时new一个动画对象并传入this(即自身指针),动画结束自动delete,全程不持有引用。这就避免了常见的循环引用导致内存泄漏。

  • resource.qrc:不是简单的“图片存放处”,而是Qt的“资源编译中枢”。它把Resource/、icons/、weatherIcon/下的所有文件打包进最终可执行文件,路径变成:/icons/sun.png。这样分发时用户不用额外拷贝图片文件夹,也不会因为相对路径错误导致图标变问号。更重要的是,它支持前缀( ),让不同模块用统一路径访问资源,比如weather.cpp里写(“:/icons/rain.png”,mainwindow里也写这个路径,无需关心物理目录结构。

提示:resource.qrc修改后必须右键→“Run qmake”,否则Qt Creator不会重新编译资源。这是新手最常忽略的一步,导致改了图标却看不到效果。

2.2 动画设计的取舍:为什么不用QML,而坚持QWidget+QPropertyAnimation

有人会问:Qt现在都推QML了,为啥还用QWidget写动画?答案很实在:学习成本和工程可控性。QML动画语法确实简洁(比如NumberAnimation { target: tempLabel; property: “opacity”; to: 1; duration: 300 }),但它把UI逻辑、动效逻辑、数据绑定全混在一起。一旦动画出问题(比如某个属性没暴露给QML),调试起来比C++还痛苦。而QWidget方案虽然代码多几行,但每一步都清晰可见:

// enterexitanimation.cpp 中的核心动画配置
QPropertyAnimation *animation = new QPropertyAnimation(this, "windowOpacity");
animation->setDuration(400); // 400毫秒完成
animation->setEasingCurve(QEasingCurve::OutInQuad); // 先慢后快再慢,模拟自然感
animation->setStartValue(0.0);
animation->setEndValue(1.0);

这段代码明确告诉你:动画操作的是windowOpacity属性(即窗口透明度),从0到1,用400毫秒,缓动曲线是OutInQuad。如果效果不满意,你直接改duration或easingCurve就行,不用去猜QML引擎内部怎么调度。更重要的是,QWidget动画可以精确控制“时机”——比如进入动画必须等窗口show()之后才启动,退出动画必须在closeEvent()里拦截并手动调用,这些在QML里反而容易失控。对于初学者,理解“属性动画”的本质(改变对象属性值+定时器驱动)比记住QML语法重要得多。

2.3 网络请求的健壮性设计:为什么不用QNetworkRequest直接发GET,而封装成WeatherManager

项目里没有出现QNetworkAccessManager *manager = new QNetworkAccessManager(this)这样的裸写法,而是通过weather.h里定义的静态方法:

// weather.h
class Weather {
public:
    static void fetchWeather(const QString &city, std::function<void(const WeatherData&)> callback);
};

这种设计解决了三个实际问题:
1. 生命周期管理:裸写的QNetworkAccessManager如果作为成员变量,可能在窗口关闭时还没收到回复,导致野指针崩溃。而静态方法内部创建的manager,在请求完成后自动delete,彻底规避内存泄漏。
2. 回调安全:使用std::function 而非直接connect信号,是因为QNetworkReply的finished()信号可能在主线程外触发,而UI更新必须在主线程。静态方法内部确保回调在主线程执行(通过QMetaObject::invokeMethod(this, [callback]{ callback(data); }, Qt::QueuedConnection))。
3. 错误隔离:网络超时、DNS失败、HTTP 404等错误统一由Weather类处理,返回给调用方一个包含errorString的WeatherData对象,mainwindow无需关心底层是curl还是Qt网络栈。

注意:Qt 6中QNetworkAccessManager的API有变化(比如不再推荐使用setSslConfiguration),但本项目兼容Qt 5.15+,关键逻辑已用宏#ifdef QT_VERSION_CHECK包裹,确保跨版本可用。

3. 核心细节解析与实操要点:从UI到动效,每个环节的“为什么这么写”

3.1 UI构建:mainwindow.ui不只是拖控件,更是状态管理的起点

打开mainwindow.ui,你会发现几个关键设计细节,它们都不是随意为之:

  • 主布局采用QVBoxLayout嵌套QHBoxLayout:顶层是垂直布局(容纳标题栏、天气信息区、操作按钮),天气信息区内部是水平布局(图标+温度+文字描述)。这种嵌套保证了窗口缩放时各元素比例协调——比如宽度变窄时,水平布局内的图标和文字会自动换行,而不是挤成一团。

  • 所有文本控件(QLabel)启用rich text:在UI编辑器里选中温度标签,勾选“Text Interaction Flags”→“TextSelectableByMouse”。这样用户可以复制温度数值(比如“23.5°C”),方便粘贴到其他地方。很多教程忽略这点,但真实应用中这是基础体验。

  • 按钮使用QToolButton而非QPushButton:刷新按钮(ui->refreshBtn)在UI文件中被设为QToolButton,并设置了icon(:/icons/refresh.png)和toolTip(“点击刷新天气”)。QToolButton默认无边框、支持图标+文字混合,视觉更轻量,符合桌面小工具定位。而QPushButton会有明显立体边框,显得笨重。

  • 窗口属性设置为WA_TranslucentBackground + FramelessWindowHint:在mainwindow.cpp构造函数中:
    cpp setAttribute(Qt::WA_TranslucentBackground); setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
    这两行决定了整个UI的“呼吸感”。WA_TranslucentBackground允许窗口背景透明(配合stylesheet里的rgba(255,255,255,180)实现磨砂效果),FramelessWindowHint去掉系统标题栏,让自绘的标题栏(ui->titleLabel)和关闭按钮(ui->closeBtn)真正掌控窗口外观。但注意:去掉标题栏后,必须手动实现拖拽移动——项目在mousePressEvent和mouseMoveEvent中做了处理:
    cpp void MainWindow::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && ui->titleBar->geometry().contains(event->pos())) { dragPosition = event->globalPos() - frameGeometry().topLeft(); event->accept(); } } void MainWindow::mouseMoveEvent(QMouseEvent *event) { if (event->buttons() & Qt::LeftButton && dragPosition.isNull() == false) { move(event->globalPos() - dragPosition); event->accept(); } }
    这段代码只在鼠标按在标题栏区域时生效,避免误触其他区域导致窗口乱飞。

3.2 资源管理:resource.qrc不是“扔进去就完事”,而是路径规范的战场

resource.qrc文件内容看似简单,但它的结构直接影响后续开发效率:

<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/icons">
    <file>icons/sun.png</file>
    <file>icons/cloud.png</file>
    <file>icons/rain.png</file>
</qresource>
<qresource prefix="/background">
    <file>background/gradient.png</file>
</qresource>
<qresource prefix="/weatherIcon">
    <file>weatherIcon/01d.png</file>
    <file>weatherIcon/02n.png</file>
</qresource>
</RCC>

这里的关键是prefix的设计哲学
- /icons:存放通用UI图标(刷新、关闭、设置)
- /background:存放窗口背景图,独立前缀便于全局替换(比如换肤功能只需改这里)
- /weatherIcon:存放天气API返回的图标代码对应图(OpenWeatherMap的01d=晴天日间),与weather.cpp中的图标映射逻辑强绑定

为什么不用统一前缀如/resources?因为这样会导致路径冗长(:/resources/icons/sun.png),且无法按用途快速定位。而分前缀后,mainwindow.cpp里写ui->iconLabel->setPixmap(QPixmap(":/icons/sun.png"));,weather.cpp里写pixmap.load(":/weatherIcon/" + iconCode + ".png");,语义清晰,不易出错。

实操心得:添加新图标时,务必先在Qt Creator里右键resource.qrc→“Add Files”,不要手动编辑XML。否则可能因路径错误导致qmake编译失败,报错信息晦涩(如“Cannot find file”)。添加后立即右键→“Run qmake”,再编译。

3.3 动画实现:enterexitanimation.h不只是“动起来”,而是状态机的雏形

enterexitanimation.h的类声明非常精简,但隐藏了关键设计:

class EnterExitAnimation : public QObject {
    Q_OBJECT
public:
    explicit EnterExitAnimation(QWidget *target, QObject *parent = nullptr);

public slots:
    void startEnterAnimation(); // 进入动画:透明度0→1,尺寸缩放0.8→1.0
    void startExitAnimation();  // 退出动画:透明度1→0,尺寸缩放1.0→0.8

signals:
    void animationFinished(); // 动画结束信号,供mainwindow监听后执行close()

private:
    QWidget *m_target;
    QPropertyAnimation *m_opacityAnim;
    QPropertyAnimation *m_scaleAnim;
    QParallelAnimationGroup *m_group; // 并行动画组,确保透明度和缩放同步
};

这里用了QParallelAnimationGroup而非单独启动两个动画,是因为单个QPropertyAnimation无法保证多个属性同时开始/结束。比如如果先启动透明度动画,再启动缩放动画,哪怕duration都是400ms,也可能因CPU调度产生1ms偏差,导致“先变透明再缩小”的诡异效果。而QParallelAnimationGroup把它们绑成一个整体,start()时一起触发,finished()时一起发出信号。

更关键的是animationFinished()信号的设计。mainwindow.cpp里这样连接:

connect(m_animation, &EnterExitAnimation::animationFinished, this, [this](){
    hide(); // 动画结束才真正隐藏窗口,避免视觉残留
    delete m_animation; // 清理内存
});

这解决了QWidget动画的经典问题:动画只是视觉效果,窗口本身还在内存里。如果不手动hide(),用户可能看到动画结束后窗口又“闪现”一下。而delete m_animation放在lambda里,确保动画对象在信号触发后立即销毁,不会因多次点击刷新按钮导致多个动画对象堆积。

3.4 天气数据处理:weather.h的接口设计,如何让JSON解析不成为噩梦

weather.h暴露的接口极其克制,只有三个核心函数:

class WeatherData {
public:
    double temperature = 0.0;
    QString condition = "Unknown";
    QString iconCode = "01d"; // OpenWeatherMap图标代码
    QString city = "Unknown";
};

class Weather {
public:
    static void fetchWeather(const QString &city, std::function<void(const WeatherData&)> callback);
    static WeatherData parseJson(const QByteArray &jsonData); // 解析入口,返回WeatherData
    static QString getIconPath(const QString &iconCode); // 根据图标代码返回资源路径
};

这种设计把“脏活”全关在内部:
- fetchWeather()封装网络请求,屏蔽QNetworkAccessManager细节;
- parseJson()用QJsonDocument解析,但对外只返回结构化的WeatherData对象,mainwindow无需接触QJsonObject;
- getIconPath()根据iconCode(如”02n”)拼接资源路径(”:/weatherIcon/02n.png”),避免mainwindow里写硬编码路径。

解析逻辑在weather.cpp中:

WeatherData Weather::parseJson(const QByteArray &jsonData) {
    QJsonParseError error;
    QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error);
    if (error.error != QJsonParseError::NoError) {
        qWarning() << "JSON parse error:" << error.errorString();
        return {}; // 返回空对象,调用方需检查
    }

    WeatherData data;
    QJsonObject root = doc.object();
    if (root.contains("name")) data.city = root["name"].toString();

    QJsonObject main = root["main"].toObject();
    if (main.contains("temp")) data.temperature = main["temp"].toDouble();

    QJsonArray weatherArray = root["weather"].toArray();
    if (!weatherArray.isEmpty()) {
        QJsonObject weatherObj = weatherArray[0].toObject();
        data.condition = weatherObj["description"].toString();
        data.iconCode = weatherObj["icon"].toString();
    }
    return data;
}

这里的关键是防御性编程:每一层访问前都用contains()检查键是否存在,避免JSON结构变动时程序崩溃。比如某些API可能不返回”main”字段,root["main"].toObject()会返回空对象,main["temp"].toDouble()则返回0.0,而不是抛异常。这对网络应用至关重要——你无法控制API是否稳定。

4. 实操过程与核心环节实现:从零编译到功能验证的完整链路

4.1 环境准备与工程导入:Qt Creator里的“三步确认法”

拿到源码包后,不要急着点编译。按以下顺序确认,能避开80%的编译失败:

  1. 确认Qt版本匹配:打开QOnlineWeather.pro文件,查看第一行:
    pro QT += core widgets network greaterThan(QT_MAJOR_VERSION, 5): QT += gui
    这表示支持Qt 5和Qt 6。但在Qt Creator中,需手动指定Kit:菜单栏→Projects→Build & Run→Kits,选择已安装的Qt版本(推荐Qt 5.15.2或Qt 6.2.4)。如果列表为空,需先在“Qt Versions”里添加qmake路径(如/usr/lib/qt5/bin/qmakeC:\Qt\6.2.4\mingw_64\bin\qmake.exe)。

  2. 确认resource.qrc已编译:右键resource.qrc→“Run qmake”。此时Qt Creator底部状态栏应显示“Running qmake… Done”。如果报错“Cannot find file”,说明resource.qrc里引用的某个文件路径错误(比如<file>icons/sun.png</file>但实际文件在Resource/icons/sun.png)。此时需打开resource.qrc,将路径改为<file>Resource/icons/sun.png</file>,再Run qmake。

  3. 确认图标资源路径正确:编译成功后运行,如果窗口内图标显示为灰色方块,大概率是资源路径问题。在mainwindow.cpp中搜索QPixmap,找到类似ui->iconLabel->setPixmap(QPixmap(":/icons/sun.png"));的代码,将:/icons/sun.png复制,粘贴到Qt Creator的“资源浏览器”(左侧边栏)顶部搜索框。如果搜索不到,说明resource.qrc的prefix或文件路径有误。

实操心得:Qt Creator的“资源浏览器”是调试资源问题的神器。它实时显示所有已编译进来的资源路径,比手动查qrc文件高效十倍。养成习惯:每次改完qrc,先在这里搜一下新路径是否存在。

4.2 网络请求调试:如何让天气数据“真正进来”,而不是卡在loading

项目默认城市是“Beijing”,但首次运行可能因网络策略失败。调试步骤如下:

  1. 开启Qt网络日志:在main.cpp开头添加:
    cpp #include <QLoggingCategory> QLoggingCategory::setFilterRules("qt.network.ssl.warning=false\nqt.network.http.warning=false");
    这样控制台会输出详细网络日志,包括URL、状态码、错误原因。

  2. 检查API密钥:项目使用OpenWeatherMap免费API,需注册获取key。在weather.cpp中搜索appid=,你会看到:
    cpp QString urlStr = QString("https://api.openweathermap.org/data/2.5/weather?q=%1&appid=YOUR_API_KEY&units=metric") .arg(city);
    YOUR_API_KEY替换成你的key(注册地址:https://home.openweathermap.org/users/sign_up)。注意:免费版限制1000次/天,且key不能暴露在公开仓库中——项目用占位符正是为了提醒你替换。

  3. 验证URL是否可访问:复制生成的完整URL(如https://api.openweathermap.org/data/2.5/weather?q=Beijing&appid=xxx&units=metric),粘贴到浏览器。如果返回JSON,说明API正常;如果返回{"cod":"404","message":"city not found"},说明城市名拼写错误(OpenWeatherMap要求英文名,北京是Beijing,不是Beijing China)。

  4. 强制刷新测试:运行程序后,点击刷新按钮,观察控制台输出。正常流程应为:
    [Debug] Network request sent to: https://... [Debug] HTTP status code: 200 [Debug] JSON parsed successfully, temperature: 23.5
    如果卡在第一行,可能是防火墙阻止了Qt程序联网(Windows Defender有时会拦截);如果第二行显示401,说明API key无效;如果第三行报JSON parse error,说明API返回了HTML错误页(常见于网络代理问题)。

4.3 动画效果调优:从“能动”到“好看”的四个参数

动画效果不理想?别改逻辑,先调这四个参数(在enterexitanimation.cpp中):

参数 当前值 调整建议 效果影响
animation->setDuration(400) 400ms 建议300~600ms 时间太短显得突兀,太长让用户等待。300ms是人眼感知流畅的阈值
animation->setEasingCurve(QEasingCurve::OutInQuad) OutInQuad 可试InOutCubicOutBounce OutInQuad最自然;InOutCubic更柔和;OutBounce有弹性感(适合趣味性应用)
m_scaleAnim->setStartValue(0.8) 0.8 可试0.7~0.9 起始缩放越小,“弹出”感越强,但过小(0.5)会导致图标模糊
m_opacityAnim->setStartValue(0.0) 0.0 必须为0.0 透明度起点必须为0,否则动画开始时有“闪现”

调整后重新编译,重点观察两个场景:
- 启动时:窗口是否从无到有,平滑浮现,无闪烁?
- 关闭时:点击关闭按钮后,窗口是否先收缩再淡出,结束时是否彻底消失(无残留)?

注意:Qt 6中QPropertyAnimation的setStartValue()对某些属性(如geometry)可能无效,需改用setKeyValueAt(0.0, value)。本项目已做兼容处理,但如果你升级Qt版本,需检查此处。

4.4 二次开发实战:三分钟接入高德地图天气API

想换掉OpenWeatherMap?以高德地图API为例(免费版日调用量1000次),只需改三处:

  1. 修改请求URL(weather.cpp):
    cpp // 替换原OpenWeatherMap URL QString urlStr = QString("https://restapi.amap.com/v3/weather/weatherInfo?city=%1&key=YOUR_AMAP_KEY&extensions=base") .arg(cityCode); // 高德需要城市编码,如北京110000

  2. 更新JSON解析逻辑(weather.cpp的parseJson函数):
    cpp // 高德返回结构:{"lives":[{"temperature":"23","weather":"晴","humidity":"45"}]} QJsonArray lives = root["lives"].toArray(); if (!lives.isEmpty()) { QJsonObject live = lives[0].toObject(); data.temperature = live["temperature"].toString().toDouble(); data.condition = live["weather"].toString(); // 高德无标准图标代码,需自行映射 data.iconCode = mapWeatherToIcon(live["weather"].toString()); }

  3. 补充图标映射函数(weather.cpp新增):
    cpp QString Weather::mapWeatherToIcon(const QString &weather) { if (weather.contains("晴")) return "sun"; if (weather.contains("雨")) return "rain"; if (weather.contains("云")) return "cloud"; return "unknown"; }
    并在resource.qrc中添加对应图标(:/icons/sun.png等)。

整个过程无需改动mainwindow、动画、资源管理任何代码,这就是模块解耦的价值。

5. 常见问题与排查技巧实录:那些让我熬夜到凌晨的坑

5.1 经典问题速查表

问题现象 可能原因 排查命令/操作 解决方案
编译报错“undefined reference to ‘Weather::fetchWeather’” weather.cpp未加入pro文件 打开QOnlineWeather.pro,检查SOURCES += ...是否包含weather.cpp 在pro文件中添加SOURCES += weather.cpp,右键→“Run qmake”
运行后窗口空白,控制台无输出 main.cpp未正确调用QApplication 检查main.cpp中QApplication a(argc, argv);w.show();是否被注释 确保这两行存在且未被#ifdef条件编译掉
天气图标显示为方块,但资源路径在资源浏览器中存在 QPixmap加载失败(如PNG格式损坏) 在mainwindow.cpp中添加qDebug() << QPixmap(":/icons/sun.png").isNull(); 如果返回true,说明图片文件损坏,重新下载或用画图软件另存为PNG
点击关闭按钮后窗口消失,但进程仍在任务管理器中 exit动画未触发delete m_animation 在enterexitanimation.cpp的startExitAnimation()末尾添加qDebug() << "Exit animation started"; 确保mainwindow.cpp中connect(m_animation, &EnterExitAnimation::animationFinished, ...)已正确连接
Qt Creator提示“Project ERROR: Unknown module(s) in QT: network” Qt安装时未勾选network模块 菜单栏→Tools→Options→Kits→Qt Versions,选中你的Qt版本,点击“Details” 重新运行Qt安装程序,勾选“Network”组件

5.2 独家避坑技巧:来自真实踩坑现场的经验

技巧1:QNetworkAccessManager的“一次一用”原则
很多教程教你在mainwindow.h里声明QNetworkAccessManager *m_network;作为成员变量,但这极易引发崩溃。正确做法是:在weather.cpp的fetchWeather()中临时创建:

QNetworkAccessManager *manager = new QNetworkAccessManager;
// ... 发送请求
// 在reply->finished()槽函数中:
QObject::connect(reply, &QNetworkReply::finished, [=]() {
    // 处理响应
    manager->deleteLater(); // 关键!确保manager在事件循环空闲时删除
});

deleteLater()delete安全,因为它把删除操作放入事件队列,避免在信号槽执行中途销毁对象。

技巧2:resource.qrc路径的“相对基准”陷阱
Qt Creator中resource.qrc的路径是相对于.pro文件所在目录的。比如你的目录结构是:

QOnlineWeather/
├── QOnlineWeather.pro
├── Resource/
│   └── icons/
│       └── sun.png
└── mainwindow.cpp

那么qrc中必须写<file>Resource/icons/sun.png</file>,而不是<file>icons/sun.png</file>。新手常犯错误是把qrc文件拖进Qt Creator后,它自动填充了错误路径,需手动修正。

技巧3:QWidget动画的“线程安全”红线
QPropertyAnimation只能在创建它的线程中运行。如果你在子线程里创建动画并start(),程序会直接crash。本项目所有动画都在主线程(GUI线程)中创建和启动,这是铁律。Qt官方文档明确警告:“Animations must be created and started in the same thread as the target object.”

技巧4:Qt 6的QString转换兼容
Qt 6中QByteArray::fromStdString()被移除。如果项目升级到Qt 6,weather.cpp中所有QString::fromStdString(jsonStr)需改为QString::fromUtf8(jsonStr.c_str())。本项目已用宏#ifdef QT_VERSION_CHECK(6, 0, 0)做了兼容,但如果你手动修改,务必检查所有字符串转换处。

5.3 性能优化建议:让小工具真正“轻量”

这个工具的目标是“小”,所以性能优化点很具体:

  • 禁用不必要的Qt模块:在QOnlineWeather.pro中,确认只启用了必需模块:
    pro QT += core widgets network # 删除 gui、sql、xml 等无关模块
    每多一个模块,可执行文件体积增加200KB+,启动时间延长50ms。

  • 图片资源压缩:Resource目录下的PNG图标,用TinyPNG在线压缩(https://tinypng.com),可减少50%体积而不损画质。实测将128x128图标从12KB压至6KB,启动速度提升约80ms。

  • 懒加载天气数据:当前是窗口show()后立即fetchWeather()。可改为“用户点击刷新按钮才请求”,并在UI上显示“点击刷新获取天气”提示。这样首次启动瞬间完成,用户体验更流畅。

  • 缓存最近一次数据:在weather.h中添加静态成员static WeatherData s_cachedData;,fetchWeather成功后缓存。下次启动时先显示缓存数据,再后台刷新,避免白屏等待。

这些优化不是锦上添花,而是让一个“玩具项目”真正具备产品思维的标志——它教会你的不仅是Qt语法,更是如何写出用户愿意长期留在桌面右下角的工具。

我在实际使用中发现,最实用的扩展不是加更多城市,而是把温度数字做成可点击的——点击一次切换摄氏/华氏,再点一次切回。这个功能只增加了12行代码(mainwindow.cpp中加一个bool成员、一个槽函数、一行connect),却让工具立刻从“展示品”变成“常用品”。技术的价值,从来不在复杂度,而在是否真正解决了人的一个小痛点。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:用Qt 5/6开发的轻量级桌面天气应用,能联网查询实时天气数据,界面基于mainwindow.ui设计,启动和关闭时有平滑动画效果(通过enterexitanimation.h/cpp实现),图标、背景图等静态资源统一打包在Resource目录下,通过resource.qrc集中管理。天气数据解析逻辑封装在weather.cpp和weater.h中,主程序入口为main.cpp,项目使用标准Qt Creator工程结构,直接打开QOnlineWeather.pro即可编译运行。配套README.txt说明基础操作,.gitignore和Makefile支持跨平台构建,适合想练手Qt界面开发、QNetworkAccessManager网络请求、资源编译和基础动画实现的学习者。所有代码模块职责明确,比如mainwindow负责UI交互,weather类处理API响应与数据映射,动画组件独立解耦,方便替换或扩展功能,比如接入不同天气API、增加城市搜索、温度单位切换等。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

更多推荐