简介:这份代码示例围绕 Qt5 多界面切换与弹窗显示展开,适合已掌握 Qt 基础、希望理解多窗口组织与交互逻辑的开发者参考。压缩包共 29 个文件,以 7 个 cpp 源文件、7 个 h 头文件和 3 个 ui 界面文件为核心,辅以 qmake 工程文件及编译中间产物,整体约 57KB,体量轻便,便于快速导入 Qt Creator 运行与调试。已有 182 人学习,说明该主题在桌面端界面开发中具有稳定的参考需求。读者可从中了解主窗口与多个子界面的切换方式、QDialog 自定义弹窗的组织形式,以及信号与槽在窗口通信中的实际用法;结合工程结构与界面文件,还能对照梳理多 UI 项目的类划分、窗口显隐控制和模态弹窗的调用思路,为后续扩展更复杂的界面流程提供可复用的代码骨架。
1. 从两个 .ui 文件开始:qt ui 多页面切换到底卡在哪
把 qt ui 从单窗口 Demo 拆成多页面工程时,最先翻车的往往不是控件摆放,而是切换后旧页面还在后台跑、弹窗关不掉、信号重复连接。这个 Qt5 资源包围绕多 .ui 界面切换和弹窗显示,把主窗口导航、页面装载、模态与非模态弹窗、返回值处理串成一条可复现的路径。它适合刚接触 Qt 界面拼装的新手,也适合手里已有原型、正被页面生命周期和弹窗时序折腾的熟手。先别急着堆控件,先把切换边界和弹窗归属看清楚。
2. QStackedWidget 落地:索引、生命周期与页面间传参
主窗口内切页面,最省心的容器是QStackedWidget。它把多个QWidget页叠在一起,只显示当前页,切换时改的是可见性,不是反复创建窗口。这个资源包的主骨架就是“一个主窗口 + 一个QStackedWidget+ 若干由 .ui 生成的页面类”,弹窗则交给QDialog单独处理。把这两类界面分开,后面排错才不会把“页面没切过去”和“弹窗没关掉”混成一锅。
2.1 为什么优先用 QStackedWidget 而不是反复 new 窗口
反复new QWidget做页面,短期看很直接,长期看会留下三个麻烦:父对象关系不清、旧页面被重复创建、信号在切换时重复连接。QStackedWidget的思路相反,页面在初始化时一次性挂进去,切换只改currentIndex或currentWidget,页面状态可以保留,返回上一页时不用重新填表单。对于设置页、首页、结果页这种主窗口内导航,它比手动show/hide更容易控制层级。
| 方案 | 页面存活方式 | 切换成本 | 适合场景 | 主要风险 |
|---|---|---|---|---|
QStackedWidget | 页面常驻,容器托管 | 低 | 主窗口内多页导航 | 页面多时首次构建变慢 |
手动show/hide | 页面常驻,自己管理 | 低 | 少量浮层、覆盖层 | 布局和层级容易乱 |
每次new QWidget | 按需创建 | 中 | 一次性向导页 | 父对象、内存、重复连接 |
QDialog | 按需弹出 | 中 | 确认、输入、提示 | 模态阻塞与返回值处理 |
这张表里最容易被忽略的是“页面常驻”带来的副作用:页面不会因为切走就销毁,所以定时器、网络请求、播放器状态都还在。我的习惯是给每个页面补一个onPageEnter()或refresh(),在currentChanged里按需调用,而不是假设页面切走就自动停下。弹窗则不要塞进QStackedWidget,它是临时交互,应该独立成QDialog,否则关闭逻辑会和页面导航缠在一起。
2.2 三个 .ui 文件挂进同一个主窗口的完整步骤
先按资源包的结构把页面类分好:MainWindow负责壳和导航,PageHome、PageSettings各管一个 .ui,DialogConfirm管弹窗。每个页面类只暴露必要的 setter 和信号,不直接把另一个页面的指针传进去。下面这套写法可以直接对照资源里的工程改,前提是 Designer 里的 objectName 要和代码一致。
第一步,把 .ui 文件加入构建清单。qmake 工程里用FORMS,CMake 工程里靠AUTOUIC,两种方式的目的都是让 uic 生成ui_*.h。如果 .ui 只在目录里、没有进构建清单,编译器就会报找不到ui_pagehome.h。
QT += core gui widgets CONFIG += c++11 TEMPLATE = app TARGET = multi_ui_demo FORMS += \ mainwindow.ui \ pagehome.ui \ pagesettings.ui \ dialogconfirm.ui HEADERS += \ mainwindow.h \ pagehome.h \ pagesettings.h \ dialogconfirm.h SOURCES += \ main.cpp \ mainwindow.cpp \ pagehome.cpp \ pagesettings.cpp \ dialogconfirm.cpp RESOURCES += resources.qrc这段 .pro 的关键参数不多:QT += widgets是 Qt5 界面模块,CONFIG += c++11让后面的 lambda 和enum class能正常编译,FORMS决定哪些 .ui 参与 uic 生成,RESOURCES决定 qrc 是否编进可执行文件。TARGET只是产物名,换成自己的工程名不影响逻辑。若工程里还有网络、数据库模块,再按需补QT += network sql,不要一次性全加上。
第二步,在主窗口里集中挂页面。下面代码假设mainwindow.ui里有stackedWidget、homeButton、settingsButton、dialogButton这几个 objectName,页面 .ui 里有对应的按钮和输入框。
// mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> class PageHome; class PageSettings; class QStackedWidget; namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: void buildPages(); Ui::MainWindow *ui; PageHome *homePage = nullptr; PageSettings *settingsPage = nullptr; }; #endif// mainwindow.cpp #include "mainwindow.h" #include "ui_mainwindow.h" #include "pagehome.h" #include "pagesettings.h" #include "dialogconfirm.h" #include <QDialog> #include <QPushButton> #include <QStackedWidget> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); buildPages(); connect(ui->homeButton, &QPushButton::clicked, this, [this] { ui->stackedWidget->setCurrentWidget(homePage); }); connect(ui->settingsButton, &QPushButton::clicked, this, [this] { ui->stackedWidget->setCurrentWidget(settingsPage); }); connect(ui->dialogButton, &QPushButton::clicked, this, [this] { DialogConfirm dlg(this); dlg.setTitleText(QStringLiteral("确认切换页面?")); dlg.setContentText(QStringLiteral("切换后当前页面的未保存内容会保留。")); if (dlg.exec() == QDialog::Accepted) { ui->stackedWidget->setCurrentWidget(settingsPage); } }); ui->stackedWidget->setCurrentWidget(homePage); } void MainWindow::buildPages() { homePage = new PageHome(this); settingsPage = new PageSettings(this); ui->stackedWidget->addWidget(homePage); ui->stackedWidget->addWidget(settingsPage); connect(homePage, &PageHome::requestOpenSettings, this, [this](const QString &userName) { settingsPage->setUserName(userName); ui->stackedWidget->setCurrentWidget(settingsPage); }); } MainWindow::~MainWindow() { delete ui; }这段代码的逻辑是:buildPages()只执行一次,把页面挂进QStackedWidget;导航按钮用setCurrentWidget(),不依赖硬编码索引;弹窗用栈对象DialogConfirm dlg(this),exec()返回后再判断结果,对象离开作用域自动析构。参数上,addWidget()返回页面索引,但这里没有保存索引,因为页面顺序以后可能调整;setCurrentWidget()比setCurrentIndex(1)更抗改动。parent设为this只是先建立父对象关系,addWidget()会把页面重新挂到 stacked widget 下,最终由容器负责析构。
第三步,让页面自己发信号,不直接操作另一个页面。下面以PageHome为例,按钮点击后把输入框内容带出去,主窗口决定要不要切页、切到哪一页。
// pagehome.h #ifndef PAGEHOME_H #define PAGEHOME_H #include <QWidget> namespace Ui { class PageHome; } class PageHome : public QWidget { Q_OBJECT public: explicit PageHome(QWidget *parent = nullptr); ~PageHome(); signals: void requestOpenSettings(const QString &userName); private: Ui::PageHome *ui; }; #endif// pagehome.cpp #include "pagehome.h" #include "ui_pagehome.h" #include <QPushButton> PageHome::PageHome(QWidget *parent) : QWidget(parent), ui(new Ui::PageHome) { ui->setupUi(this); connect(ui->goSettingsButton, &QPushButton::clicked, this, [this] { const QString name = ui->userNameEdit->text().trimmed(); emit requestOpenSettings(name); }); } PageHome::~PageHome() { delete ui; }本文还有配套的精品资源,点击获取