ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于QListWidget的Qt天气客户端:架构设计与信号槽联动解析

基于QListWidget的Qt天气客户端:架构设计与信号槽联动解析 简介基于Qt和QListWidget实现的天气预报系统毕业设计完整提供C源码与配套文档主要面向计算机、通信、人工智能、自动化等专业的学生和开发者既可作为课程设计、大作业的参考也适合Qt初学者从零学习项目架构与界面开发。资源包共74个文件涵盖C头文件、源文件、Qt界面UI文件、项目配置文件、图片素材png/jpg、Markdown文档及可直接运行的exe程序整体打包约21.31MB结构与类型清晰便于按需查阅。项目已调试测试、可正常编译运行作者曾凭此获得答辩98分系统内部基于QListWidget实现城市天气列表包含天气实时展示、空气质量、湿度、风速、温度趋势等模块配合文档说明可快速理解数据解析、UI布局和信号槽机制。目前已有88人浏览学习适合需要快速上手Qt项目或完成相关课题设计的用户学习借鉴也可在此基础上扩展地图定位、多日预报等功能。1. 从 QListWidget 切入的 Qt 天气客户端手头这份基于 Qt Widgets 框架的天气预报系统源码是一个可以直接跑起来的完整 Qt 工程。它不依赖 WebEngine 或 QML所有界面交互都基于 QListWidget 完成城市切换和天气数据联动数据解析走的是 Qt 原生的 JSON 模块资源文件统一收在 .qrc 里。对于正在准备 C 课程设计或毕业设计的同学来说这套代码的价值在于你能在一份不到两千行的代码里完整看到 Widgets 应用程序从工程配置、UI 设计、数据建模到事件联动的全链路写法。更重要的是它把 QListWidget 这个高频控件从「能显示列表」到「承载业务数据并驱动界面跳转」的用法演示得很完整——这恰好是很多 Qt 初学者卡住的地方。下文从架构设计讲起逐步拆到具体的信号槽连接和数据解析最后给出发布部署和二次开发的落地方案。注意项目名为 newWeather.pro说明原作者在 Qt Creator 中做过多次工程调整文件名里同时有 citywithtemp.ui、asiawea.ui、worldlistwea.ui 三个界面文件对应三层天气展示逻辑这个结构是理解整个工程的钥匙。2. 工程结构与数据层的三层拆分2.1 从 .pro 文件和文件清单反推模块职责拿到压缩包后先看 newWeather.pro 和 .ui 文件基本能还原出原作者的工程组织方式。整个程序按「主窗口 城市温度列表 亚洲天气 世界城市列表」划分四个 UI 文件对应四个界面入口共享同一套天气数据结构。文件清单里出现的 weatherData.h、weatherTool.h 是纯逻辑层不依赖任何界面类这是很值得学习的一点数据模型和界面展示完全分离。文件职责依赖关系mainwindow.h/cpp/ui主窗口城市输入、QListWidget 列表、天气展示依赖 weatherData.hcitywithtemp.h/cpp/ui城市与温度对照列表视图依赖 weatherTool.hasiawea.h/cpp/ui亚洲主要城市天气面板依赖 weatherData.hworldlistwea.h/cpp/ui世界城市列表与天气搜索依赖 weatherData.hweatherData.hWeatherData 结构体、天气代码映射无界面依赖weatherTool.h温度换算、空气质量等级计算工具无界面依赖res.qrc图片、背景图、图标资源索引供所有 UI 引用这个拆法对课程设计级别的项目非常合适每个类只干一件事答辩时被问到任意一个类的职责都能快速说清。相比之下很多新手会把所有控件和逻辑塞进 mainwindow 一个类里代码量一上去就失控了。文件结构本身就是课程设计的加分项。2.2 WeatherData 结构体的字段设计打开 weatherData.h数据结构覆盖了一个天气应用最核心的展示字段。从资源文件里的图标名可以反向推断字段构成sunrise.png 对应日出日落wind.png 对应风力风速humidity.png 和 lowHumidty.png 对应湿度区间pm25.png 和 aqi.png 对应空气质量和 PM2.5。一个典型的实现会长这样struct WeatherData { QString city; // 城市名 QString date; // 日期 QString week; // 星期 int tempHigh; // 最高温度 int tempLow; // 最低温度 int humidity; // 湿度百分比 int windLevel; // 风力等级 QString windDir; // 风向 int pm25; // PM2.5 指数 int aqi; // 空气质量指数 QString weatherType; // 天气类型晴/多云/雨等 QString weatherCode; // 天气代码用于图标匹配 QString sunrise; // 日出时间 QString sunset; // 日落时间 bool isValid() const { return !city.isEmpty(); } };字段的类型选择有一个细节值得注意温度和 PM2.5 用 int湿度用 int但天气类型和风向全部用 QString。这样设计的好处是后续在 UI 上显示时不需要做额外的类型转换直接用QString::number(tempHigh)就能拼到标签上。反而如果一开始图省事把所有字段都声明为 QString后面做温度比较或排序时会非常痛苦。2.3 qrc 资源系统与 UI 解耦源码目录里的背景图、天气图标、按钮图标全部通过 res.qrc 统一索引这让程序发布时只需要带一个可执行文件加资源文件不需要散落一大堆图片。qrc 文件内部实际是 XML 格式核心是 alias 机制RCC qresource prefix/ file aliasbg.jpgres/bg2.jpg/file file aliasicon-sunres/WeaFor.png/file file aliasicon-searchres/Search.png/file file aliasicon-backres/back.png/file file aliasicon-windres/wind.png/file /qresource /RCC用 alias 给资源取别名之后代码里统一用:/icon-sun引用后续想换图片只要改 qrc 映射不用动任何 C 代码。mainwindow.cpp 里加载图片的做法一般是QPixmap(:/bg.jpg)注意冒号前缀是 Qt 资源系统的固定语法少了冒号路径就失效了。这个细节在答辩演示时经常被追问答上来很容易加分。3. QListWidget 实现城市列表与实时天气联动3.1 初始化列表并绑定城市数据QListWidget 在这个项目里承担的任务不是简单的文本展示而是「选择城市 → 触发天气数据切换」的交互入口。主窗口初始化时先给 QListWidget 设置标题、添加城市项再把城市名以用户数据的形式存到每个 QListWidgetItem 里// mainwindow.cpp 构造函数片段 void MainWindow::initCityList() { ui-listWidget-setSpacing(6); // 每个列表项之间的间距 ui-listWidget-setIconSize(QSize(48, 48)); // 天气图标大小 ui-listWidget-setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); QStringList cities { 北京, 上海, 广州, 深圳, 成都, 杭州 }; for (const QString city : cities) { auto *item new QListWidgetItem(ui-listWidget); item-setText(city); // 列表项显示名称 item-setData(Qt::UserRole, city); // 用户数据里也存一份供后续取用 item-setSizeHint(QSize(0, 56)); // 控制列表项高度保证点击区域 item-setIcon(QIcon(:/icon-sun)); // 默认图标 ui-listWidget-addItem(item); } }三个关键参数的选择逻辑setSpacing(6)是增大列表项之间的视觉间隔避免触摸或鼠标点击时误触相邻项setSizeHint强制每一行高度为 56 像素这样整个列表在 1080p 屏幕上不需要滚动就能展示 6 个城市Qt::UserRole是 Qt 预留的自定义数据起始位置Qt::UserRole 1 及以后的值都可以自由使用项目中存一份城市名是为了在信号处理函数里直接取出数据避免再去解析 item-text()。3.2 信号槽连接与当前项变化响应城市列表的数据绑定做完后核心是连接 QListWidget 的信号。项目里用的是currentItemChanged而不是itemClicked区别在于前者在键盘上下键切换时也触发后者只响应鼠标点击。两者接法相同// mainwindow.cpp connect(ui-listWidget, QListWidget::currentItemChanged, this, MainWindow::onCurrentCityChanged); void MainWindow::onCurrentCityChanged(QListWidgetItem *current, QListWidgetItem *previous) { if (!current) return; // 列表被清空时 current 为空直接返回 Q_UNUSED(previous); // 前一项在当前场景用不到但信号要求的参数不能省 QString city current-data(Qt::UserRole).toString(); WeatherData data fetchWeatherData(city); // 根据城市拉取对应的天气数据 updateWeatherPanel(data); // 刷新右侧天气面板 }注意currentItemChanged信号会携带 old 和 new 两个 QListWidgetItem 指针如果列表清空后重新装载数据current指针可能为空必须在函数开头判空否则会触发空指针崩溃。项目里fetchWeatherData返回的是 WeatherData 结构体按值传递在这里比按引用更安全因为数据源自内部容器按值返回后即使容器被清理也不影响界面层。3.3 列表项数据更新与局部刷新天气数据更新后不需要重建整个 QListWidget只需更新对应项的文本和图标。这里有一个性能相关的细节不要调用ui-listWidget-clear()再重新 addItem这会触发 currentItemChanged 信号多次且界面会闪烁。正确做法是用takeItem或直接更新项数据void MainWindow::updateCityWeatherIcon(const QString city, const QString weatherCode) { for (int i 0; i ui-listWidget-count(); i) { QListWidgetItem *item ui-listWidget-item(i); if (item-data(Qt::UserRole).toString() city) { QString iconPath QString(:/type-%1).arg(weatherCode); item-setIcon(QIcon(iconPath)); break; } } }这里遍历 QListWidget 的所有项逐个比对 UserRole 数据。城市数量少项目里最多十几个O(n) 遍历完全够用。如果城市数量达到几百上千就该换成 QMapQString, QListWidgetItem* 做映射但在这个项目规模下不必过度设计。用item(i)配合count()是操作 QListWidget 的标准遍历方式注意item(x)返回的是指针越界时返回 nullptr。3.4 列表切换时的三个典型坑第一个坑是 currentItemChanged 在窗口构造阶段就会触发一次。构造函数里执行addItem时第一项会被自动设为当前项触发信号此时 if!current判空能挡住一部分但最好在initCityList()完成之前用blockSignals(true)临时屏蔽ui-listWidget-blockSignals(true); // addItem 循环 ui-listWidget-blockSignals(false);第二个坑是清空列表时的空指针。调用clear()时 currentItemChanged 也会触发旧的指针已经失效严格说此时current为 nullptr判空是最稳妥的。第三个坑是 UI 文件里 QListWidget 的currentRow设计。如果城市数据是动态加载的建议在 UI 设计器里把currentRow初始值设为 -1避免默认选中第一项导致提前触发一次界面刷新。4. 多界面联动与天气数据解析的代码实现4.1 三层界面切换的架构逻辑citywithtemp.ui、asiawea.ui、worldlistwea.ui 三个界面文件的共存是这个项目区别于一般单窗口天气程序的核心。主窗口左侧是 QListWidget 城市栏右侧除了天气详情面板还有打开城市温度列表和亚洲天气面板的入口。三个子界面之间不直接通信全部通过主窗口中转这是一种星形结构——主窗口是唯一的数据分发中心。void MainWindow::openCityWithTemp() { if (!cityWithTempWindow) { cityWithTempWindow new CityWithTemp(this); connect(cityWithTempWindow, CityWithTemp::citySelected, this, MainWindow::onSubCitySelected); } cityWithTempWindow-refreshCityList(ui-listWidget-count()); cityWithTempWindow-show(); } void MainWindow::onSubCitySelected(const QString city) { // 子界面选中的城市回填到主窗口列表 auto *item ui-listWidget-currentItem(); if (item) { item-setText(city); item-setData(Qt::UserRole, city); onCurrentCityChanged(item, nullptr); } }一个很细但很关键的设计子窗口使用new创建但不立即释放而是在主窗口析构时通过 parent 关系统一回收。这避免了每次切换城市都要整体重建子界面换来的代价是一对一的信号槽连接必须手动做断开处理防止窗口关闭后还能收到信号。代码里citySelected信号在 QListWidget 的itemDoubleClicked中发出而不是单击这是为了防止误触。4.2 JSON 天气数据的字段映射与解析weatherData.h 中的结构体字段最终要映射到网络上天气 API 或本地数据文件里的 JSON 字段。这类项目最常见的数据格式是下面这样的嵌套 JSON{ city: 北京, daily: [ { date: 2024-05-20, week: 周一, dayType: 晴, nightType: 多云, high: 28, low: 17, humidity: 32, pm25: 45, aqi: 68, windDir: 南风, windLevel: 3, sunrise: 05:00, sunset: 19:26 } ] }Qt 5.15 以上版本用 QJsonDocument 解析整个解析函数写成一个静态工具函数放回 weatherData.h 中界面类调用时完全不需要知道 JSON 结构这是值得推广的做法// weatherData.h 中的解析函数 static WeatherData parseWeatherJson(const QByteArray jsonData) { WeatherData data; QJsonDocument doc QJsonDocument::fromJson(jsonData); if (doc.isNull() || !doc.isObject()) return data; QJsonObject root doc.object(); data.city root.value(city).toString(); QJsonArray dailyArr root.value(daily).toArray(); if (dailyArr.isEmpty()) return data; QJsonObject day dailyArr.at(0).toObject(); data.date day.value(date).toString(); data.week day.value(week).toString(); data.tempHigh day.value(high).toInt(); data.tempLow day.value(low).toInt(); data.humidity day.value(humidity).toInt(); data.pm25 day.value(pm25).toInt(); data.aqi day.value(aqi).toInt(); data.windDir day.value(windDir).toString(); data.windLevel day.value(windLevel).toInt(); data.weatherType day.value(dayType).toString(); data.sunrise day.value(sunrise).toString(); data.sunset day.value(sunset).toString(); // 天气代码映射0-4 晴到多云5-7 雨雪 int code typeToCode(data.weatherType); data.weatherCode QString::number(code); return data; }toInt()和toString()有一个重要行为当 JSON 中缺少某个字段时toInt()返回 0toString()返回空字符串而不是抛异常或崩溃。所以解析函数不需要对每个字段判空只需要在isNull()层面拦截一次。typeToCode是一个内部映射函数把中文天气类型映射成数字代码再与资源文件里的type-N.png图标对应。这个映射是界面显示和天气图标匹配的关键要保证中文字段和 API 返回类型一致。4.3 资源路径加载失败时的排查手段资源加载失败是这类项目最常见的运行异常图片不显示、按钮没图标、背景黑屏。排查路径有一个标准流程先看编译输出中有没有 qrc 编译警告再看代码里引用的资源路径是否与 qrc 文件中的 alias 一致最后用以下代码在运行时打印资源目录qDebug() Resource paths:; for (const QString key : QDir(:/).entryList()) { qDebug() key; }如果输出为空说明 qrc 文件没有参与编译检查 .pro 里是否写入了RESOURCES res.qrc。如果只输出了根路径下的第一层资源检查 alias 是否正确。这个排查思路对任何 Qt 工程通用——先确认资源文件被编进二进制再确认路径引用与 qrc 中的 key 一致。4.4 温度与空气质量的展示逻辑citywithtemp.cpp 里的核心功能是城市与温度对照本质上是对 WeatherData 的 tempHigh 和 tempLow 做排序和颜色标注。低温度显示蓝色高温度显示红色这个逻辑很适合抽成一个工具函数放进 weatherTool.hQString colorForTemperature(int temp) { if (temp 0) return #4A90D9; // 冰点以下用蓝色 if (temp 15) return #50B432; // 低温用绿色 if (temp 28) return #F0A500; // 适中用橙色 return #E64545; // 高温用红色 }温度区间的阈值不是固定标准不同地区体感差异较大实践中常以 0°C、15°C、28°C 作为分界。这个函数放在纯逻辑层的好处是后续如果要适配不同地域标准只需要改这一个函数。AQI 指标同理可以用switch把 aqi 值映射为优/良/轻度污染/中度污染/重度污染五档每个档位对应一种颜色和一段提示文案。项目里的 aqi.png、pm25.png 图标配合 QLabel 的setPixmap和setText组合使用效果上比纯文本更有直观感。5. 发布部署与二次开发的四个关键点完成编码和调试之后课程设计交付最重要的是「能演示、能打包、能讲清扩展点」。以下四件事决定你的程序换个电脑能否直接运行。5.1 windeployqt 一键部署项目文件夹里出现的 Setup.exe 说明原工程做过发布打包。Qt 程序发布最稳妥的方式是使用官方工具 windeployqt在 Qt 命令行工具里执行cd build-newWeather-Desktop_Qt_5_15_2_MinGW_64_bit-Release windeployqt newWeather.exe执行后工具会自动把 Qt5Core.dll、Qt5Widgets.dll、Qt5Gui.dll 以及 platforms 目录下的 qwindows.dll 拷贝到 exe 同级目录。发布后的目录结构应当如下release/ ├── newWeather.exe ├── Qt5Core.dll ├── Qt5Widgets.dll ├── Qt5Gui.dll └── platforms/ └── qwindows.dll如果目标机器缺少某个 DLL双击 exe 会弹窗报「找不到 Qt5Core.dll」。这时回到 Qt 安装目录的 bin 目录手动拷贝对应 DLL 即可。平台插件 platforms/qwindows.dll 缺失时Qt 程序会直接报「could not find or load the Qt platform plugin windows」这是新手最容易碰到的发布坑。5.2 Release 与 Debug 的选择工程编译时务必选择 Release 模式交付Debug 模式的 exe 体积大一到两倍运行速度更慢且依赖的调试版 DLL 不能和生产环境混用。在 .pro 文件中加入编译宏控制有助于跨平台编译CONFIG release CONFIG - debug_and_release5.3 接入真实天气数据源的扩展点当前项目里的天气数据是本地模拟或手动维护的 JSON二次开发最自然的路径是接入真实网络 API。扩展时只需替换fetchWeatherData函数的内部实现用 QNetworkAccessManager 发起 HTTP 请求把返回的 JSON 交给parseWeatherJson解析即可QNetworkAccessManager *manager new QNetworkAccessManager(this); connect(manager, QNetworkAccessManager::finished, this, [](QNetworkReply *reply) { if (reply-error() QNetworkReply::NoError) { WeatherData data parseWeatherJson(reply-readAll()); updateWeatherPanel(data); } reply-deleteLater(); }); manager-get(QNetworkRequest(QUrl(https://api.example.com/weather?citybeijing)));QNetworkAccessManager 是异步的不能直接 return 拿数据要通过 lambda 或槽函数接收。注意回调里执行 updateWeatherPanel 属于跨线程到主线程的操作不需要额外加信号槽因为 QNetworkAccessManager 的回调本身就发生在主线程事件循环中。5.4 答辩演示时的数据重置技巧在本地 Demo 中演示时每次启动程序都重置天气数据到初始城市列表代码里在initCityList()的入口调用一句disconnect()并重新连接信号槽就能避免演示过程训练数据混乱disconnect(ui-listWidget, QListWidget::currentItemChanged, this, MainWindow::onCurrentCityChanged); initCityList(); connect(ui-listWidget, QListWidget::currentItemChanged, this, MainWindow::onCurrentCityChanged);这样保证第二次加载城市列表时不会因为旧信号槽存在而触发两次刷新。三个子界面文件citywithtemp、asiawea、worldlistwea的扩展点同理保持它们与主窗口通过信号槽通信子界面内部可以自由替换为表格控件 QTableWidget 或图表控件主窗口代码完全不需要改动。本文还有配套的精品资源点击获取
返回列表