Qt实战:从登录界面到多窗口跳转的C++桌面应用开发指南
1. 项目缘起与核心价值
最近在带几个刚入行的C++开发新人,发现他们虽然语法过关,但一碰到要动手做个带界面的、能交互的完整小项目就有点懵。很多人学了一堆std::vector、std::map,但不知道怎么把这些知识串起来,变成一个用户能点、能看、能用的东西。这让我想起自己刚接触Qt那会儿,也是从做一个最简单的登录界面,然后跳转到主页面开始的。这个看似基础的需求,实际上是一个绝佳的“麻雀虽小,五脏俱全”的练手项目。它逼着你必须去理解Qt的信号与槽机制、界面布局管理、窗口的生命周期,以及如何组织一个结构清晰的C++项目。这远比写一个控制台的计算器要有价值得多。
所以,今天我就以“实现用户登录页面及多个界面跳转”这个实战项目为例,带大家走一遍完整的开发流程。我们不止是画两个窗口、写两行连接代码就完事。我会重点拆解那些新手最容易卡壳的地方:比如登录验证逻辑怎么写才安全又清晰?多个窗口之间如何优雅地传递数据?如何管理窗口的创建与销毁,避免内存泄漏?这些都是在实际工作中必然会遇到的“硬骨头”。通过这个项目,你能把C++的面向对象思想、Qt的核心框架,以及软件工程的基本模块化思维都实践一遍。无论你是想找一份C++/Qt开发的工作,还是想自己做个桌面小工具,这篇文章都能给你一套可以直接“抄作业”的完整方案。
2. 项目架构设计与环境搭建
在动手写代码之前,我们先得把“房子”的图纸画好。一个混乱的项目结构,会让后续的开发和维护变得异常痛苦。对于这个登录跳转项目,我推荐采用一种清晰的三层结构,这能很好地体现关注点分离的思想。
2.1 项目目录结构规划
我通常会这样组织我的Qt项目目录:
LoginDemo/ ├── LoginDemo.pro # Qt项目文件 ├── src/ # 源代码目录 │ ├── main.cpp # 程序入口 │ ├── core/ # 核心业务逻辑(非UI相关) │ │ ├── User.h │ │ └── User.cpp # 用户模型,封装用户名、密码等数据 │ ├── ui/ # 界面类 │ │ ├── LoginDialog.h │ │ ├── LoginDialog.cpp │ │ ├── MainWindow.h │ │ └── MainWindow.cpp │ └── utils/ # 工具类(可选) │ └── Settings.h # 用于读写配置文件,如记住密码 ├── forms/ # Qt Designer设计的.ui文件 │ ├── LoginDialog.ui │ └── MainWindow.ui ├── resources/ # 资源文件(图片、qss样式表等) │ └── images/ └── README.md为什么这么设计?
src/core/:存放纯粹的数据模型和业务逻辑。比如User类,它只负责保存用户信息、验证密码(这里指逻辑验证,非网络请求),不关心自己是在哪个窗口被使用。这样,即使哪天我们把Qt界面换成其他GUI库,或者需要为这个逻辑写单元测试,这部分代码都可以直接复用。src/ui/:存放与界面强相关的类。它们继承自QWidget或QDialog,负责控件的布局、信号槽的连接、以及调用core中的业务逻辑。界面类应该尽可能“瘦”,只处理显示和用户交互。forms/:这是Qt Designer生成的.ui文件。强烈建议将界面设计和业务逻辑分离。用Designer拖拽完成90%的布局工作,然后在对应的ui类里用ui->objectName来访问控件。这比全部用代码写布局要高效、易维护得多。- 分离的好处:当产品经理要求把登录按钮从蓝色改成绿色,你只需要改
.ui文件或对应的样式表,不需要动C++业务代码。当需要修改密码加密方式时,你也只需要修改core/User.cpp,不影响界面。
2.2 Qt开发环境的选择与配置
工欲善其事,必先利其器。对于Qt新手,我首推Qt Creator作为集成开发环境。它和Qt框架是天作之合,对.ui文件、资源文件、信号槽连接都有非常好的内置支持,能极大降低学习成本。
安装要点:
- 下载:去Qt官网下载在线安装器。注意,对于商业项目要留意开源协议(LGPLv3)。个人学习和开源项目一般用开源版即可。
- 组件选择:在安装时,你会看到一个长长的组件列表。对于这个项目,确保勾选以下关键组件:
- Qt 6.x(如Qt 6.5 LTS):选择一个长期支持版本,更稳定。Qt 6是主流,新项目建议直接上6。
- MSVC 2019/2022 64-bit:如果你在Windows上,这是配合Visual Studio编译器的套件。也可以选择MinGW套件,但MSVC在Windows上兼容性更好。
- Qt Creator:这个IDE本身。
- Sources:Qt的源代码。勾选它,以后在Qt Creator里按F2可以跳转到Qt类的内部实现,对于深入学习至关重要。
- 环境变量:安装程序通常会帮你设置好。安装完成后,打开Qt Creator,在“工具”->“选项”->“Kits”里,应该能看到一个自动检测到的桌面套件(Desktop Qt x.x.x MSVC2019 64bit)。如果没看到,可能需要手动配置编译器和Qt版本路径。
关于编译器:如果你没有安装Visual Studio,Qt安装程序可能会为你安装一个MinGW编译器。也可以。但请注意,用MSVC编译的第三方库(如OpenCV)可能无法直接在MinGW项目中使用,反之亦然。保持开发环境的一致性很重要。
3. 登录界面(LoginDialog)的精细化实现
登录界面是用户的第一道关卡,它的体验至关重要。我们不仅要实现功能,还要考虑一些细节体验。
3.1 使用Qt Designer高效构建UI
打开Qt Creator,新建一个Qt Widgets Application项目。在创建过程中,向导会问你需要哪些基类,对于登录对话框,选择QDialog作为基类,并命名为LoginDialog。这样会自动生成.h,.cpp和.ui文件。
双击生成的LoginDialog.ui文件,Qt Designer界面就会打开。我们从左侧的Widget Box拖拽控件:
- 拖入两个
QLabel,分别写上“用户名:“和”密码:“。 - 拖入两个
QLineEdit,放在Label旁边。将用于密码的QLineEdit的echoMode属性设置为Password,这样输入时就显示为圆点。 - 拖入一个
QCheckBox,命名为checkBoxRemember,文本设为“记住密码”。 - 拖入一个
QPushButton,命名为pushButtonLogin,文本设为“登录”。 - 使用布局管理器(Layouts):选中所有控件,右键选择“布局”->“栅格布局”或“垂直布局”,让界面能随着窗口大小自适应。再给整个对话框设置一个合适的布局。
设计技巧:给控件起一个有意义的objectName,比如用户名输入框叫lineEditUsername,密码框叫lineEditPassword。这会在自动生成的ui_LoginDialog.h文件中创建对应的成员变量,我们在代码中可以通过ui->lineEditUsername来直接访问,非常方便。
3.2 登录逻辑的核心:信号、槽与数据验证
界面画好后,回到LoginDialog的C++类中实现逻辑。头文件LoginDialog.h大概长这样:
#ifndef LOGINDIALOG_H #define LOGINDIALOG_H #include <QDialog> namespace Ui { class LoginDialog; } class LoginDialog : public QDialog { Q_OBJECT // 所有QObject派生类都必须有这个宏,用于支持信号槽和元对象系统 public: explicit LoginDialog(QWidget *parent = nullptr); ~LoginDialog(); private slots: void on_pushButtonLogin_clicked(); // Qt Creator会自动关联的槽函数命名格式 void on_checkBoxRemember_stateChanged(int state); private: Ui::LoginDialog *ui; void loadSavedSettings(); // 加载保存的设置(如记住的用户名) void saveSettings(); // 保存设置 }; #endif // LOGINDIALOG_H重点在LoginDialog.cpp的实现:
#include "LoginDialog.h" #include "ui_LoginDialog.h" #include <QSettings> #include <QMessageBox> #include <QDebug> LoginDialog::LoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::LoginDialog) { ui->setupUi(this); // 设置窗口属性 setWindowTitle("用户登录"); setFixedSize(400, 300); // 固定窗口大小,防止被拉变形 // 连接信号与槽 // 方式1:使用Qt Designer自动连接(命名规则:on_控件名_信号名) // 只要槽函数严格按照 on_pushButtonLogin_clicked() 命名,无需手动connect。 // 方式2:手动连接(更灵活,推荐在构造函数中显式写出,清晰明了) // connect(ui->pushButtonLogin, &QPushButton::clicked, this, &LoginDialog::on_pushButtonLogin_clicked); // 加载上次保存的设置 loadSavedSettings(); } LoginDialog::~LoginDialog() { delete ui; } void LoginDialog::loadSavedSettings() { QSettings settings("MyCompany", "LoginDemo"); // 组织名,应用名 bool isRemember = settings.value("RememberPassword", false).toBool(); QString savedUsername = settings.value("Username", "").toString(); ui->lineEditUsername->setText(savedUsername); ui->checkBoxRemember->setChecked(isRemember); if (isRemember && !savedUsername.isEmpty()) { // 注意:真实场景下,密码绝不应以明文保存! // 这里仅为演示。生产环境应使用操作系统提供的安全凭证存储(如Windows Credential Manager, macOS Keychain)。 QString savedPassword = settings.value("Password", "").toString(); ui->lineEditPassword->setText(savedPassword); ui->lineEditPassword->setFocus(); // 焦点移到密码框 } else { ui->lineEditUsername->setFocus(); // 焦点移到用户名框 } } void LoginDialog::saveSettings() { QSettings settings("MyCompany", "LoginDemo"); QString username = ui->lineEditUsername->text().trimmed(); bool isRemember = ui->checkBoxRemember->isChecked(); settings.setValue("Username", isRemember ? username : ""); // 不记住则清空用户名 if (isRemember) { // 警告:明文存储密码是极其危险的行为!此处仅为流程演示。 // 实际应存储密码的加盐哈希值,用于下次登录时比对,而非存储密码本身。 settings.setValue("Password", ui->lineEditPassword->text()); settings.setValue("RememberPassword", true); } else { settings.remove("Password"); // 移除保存的密码 settings.setValue("RememberPassword", false); } } void LoginDialog::on_checkBoxRemember_stateChanged(int state) { // 状态改变时,可以即时给出提示 if (state == Qt::Checked) { // 可以弹出一个提示框,告知用户风险(如果存储明文) // QMessageBox::information(this, "提示", "请注意,在本电脑上记住密码可能存在安全风险。"); } } void LoginDialog::on_pushButtonLogin_clicked() { QString username = ui->lineEditUsername->text().trimmed(); QString password = ui->lineEditPassword->text(); // 1. 前端基础验证 if (username.isEmpty()) { QMessageBox::warning(this, "输入错误", "用户名不能为空!"); ui->lineEditUsername->setFocus(); return; } if (password.isEmpty()) { QMessageBox::warning(this, "输入错误", "密码不能为空!"); ui->lineEditPassword->setFocus(); return; } // 2. 模拟业务逻辑验证 // 这里应该是网络请求或数据库查询。我们用一个简单的本地模拟。 // 假设正确用户是 admin/123456 bool loginSuccess = false; if (username == "admin" && password == "123456") { loginSuccess = true; } // 你可以在这里扩展,比如从文件或一个简单的用户列表里读取验证信息。 if (loginSuccess) { // 3. 登录成功 qDebug() << "用户" << username << "登录成功"; saveSettings(); // 保存“记住密码”等设置 // 4. 发出自定义信号(如果需要通知父窗口) // emit loginSuccessful(username); // 5. 接受对话框,返回 QDialog::Accepted accept(); } else { // 登录失败 QMessageBox::critical(this, "登录失败", "用户名或密码错误!"); ui->lineEditPassword->clear(); ui->lineEditPassword->setFocus(); // 不要在这里调用 reject(),让用户有机会重试 } }关键点解析与避坑指南:
QSettings的使用:它提供了跨平台的配置存储,在Windows上存在注册表,在macOS/Linux上存在~/.config目录下的ini文件。“MyCompany”和“LoginDemo”决定了存储路径。- 密码存储的安全警告:代码中我反复强调了绝对不要明文存储密码。这是一个原则性问题。正确的做法是,在用户第一次登录并勾选“记住密码”时,将密码的加盐哈希值存储起来。下次登录时,对用户输入的密码进行同样的哈希计算,与存储的哈希值比对。这样即使配置文件泄露,攻击者也无法得到原始密码。对于真正的生产环境,应考虑使用操作系统级别的安全存储API。
accept()与reject():这是QDialog的两个重要槽函数。调用accept()会关闭对话框并返回QDialog::Accepted,通常在成功时调用。调用reject()则返回QDialog::Rejected。在主函数中,我们可以通过dialog.exec()的返回值来判断用户是登录成功还是取消了登录。- 输入验证:在发起真正的验证(如网络请求)之前,先做前端的基础验证(非空、格式等),这能提供更快的用户反馈并减少不必要的后端压力。
- 焦点控制:使用
setFocus()在验证失败后或初始化时,将输入焦点放到合适的控件上,提升用户体验。
4. 主界面(MainWindow)与多窗口跳转管理
登录成功后,我们进入主界面。主界面通常是一个QMainWindow,它提供了菜单栏、工具栏、状态栏和中心部件的框架。
4.1 创建功能丰富的主窗口
同样,先用Qt Designer设计MainWindow.ui。你可以添加菜单(QMenuBar),比如“文件”->“退出”;添加工具栏(QToolBar),放几个常用动作的按钮;中间的中心区域可以放一个QTabWidget用于承载多个子功能页面,或者直接放一些功能控件。
在MainWindow类中,我们需要处理菜单动作的触发。例如,在MainWindow的构造函数中:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); setWindowTitle("主界面 - 欢迎您"); // 连接菜单动作 connect(ui->actionExit, &QAction::triggered, this, &MainWindow::close); // “退出”动作直接关闭窗口 connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::showAboutDialog); // ... 其他连接 } void MainWindow::showAboutDialog() { QMessageBox::about(this, "关于", "这是一个Qt登录跳转示例程序。"); }4.2 窗口跳转的核心:生命周期与数据传递
这是本项目的核心难点。如何从登录窗口跳转到主窗口,并优雅地关闭登录窗口?
方案一:栈式管理(最常用、最清晰)在main.cpp或一个应用程序控制器中管理窗口的生命周期。
// main.cpp #include "LoginDialog.h" #include "MainWindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); LoginDialog loginDialog; MainWindow mainWindow; // 显示登录对话框 if (loginDialog.exec() == QDialog::Accepted) { // 登录成功,显示主窗口 mainWindow.show(); // 进入主事件循环 return a.exec(); } else { // 登录被取消或失败,直接退出程序 return 0; } }优点:逻辑简单直接,生命周期清晰。登录窗口loginDialog是局部变量,exec()返回后即被销毁。主窗口mainWindow作为局部变量,在main函数结束时销毁。缺点:主窗口和登录窗口耦合在main函数里。如果后续有更多窗口跳转(如从主窗口跳到设置窗口),逻辑会变得复杂。
方案二:动态创建与指针管理在堆上动态创建窗口,并用智能指针或父对象管理其生命周期。
// 在LoginDialog登录成功的槽函数中 void LoginDialog::on_pushButtonLogin_clicked() { // ... 验证逻辑 ... if (loginSuccess) { saveSettings(); // 创建主窗口 MainWindow *mainWin = new MainWindow(); // 在堆上创建 mainWin->setAttribute(Qt::WA_DeleteOnClose); // 设置关闭时自动删除 mainWin->show(); this->close(); // 关闭登录窗口 // 注意:此时LoginDialog对象可能还未销毁(如果它是栈对象), // 但窗口已关闭。更好的做法是发出信号,让拥有它的管理者来关闭它。 } }或者,更优雅的方式是,登录对话框不负责创建主窗口,它只负责报告结果:
// LoginDialog.h signals: void loginSuccess(const QString &username); // 声明一个成功信号 // 在登录成功的槽函数中 emit loginSuccess(username); // 发出信号 this->accept(); // 对话框关闭 // 在main.cpp或一个AppController类中 LoginDialog dlg; MainWindow *mainWin = nullptr; // 主窗口指针 // 连接信号 QObject::connect(&dlg, &LoginDialog::loginSuccess, [&](const QString &name){ if (!mainWin) { mainWin = new MainWindow(); mainWin->setAttribute(Qt::WA_DeleteOnClose); mainWin->setWindowTitle("欢迎," + name); mainWin->show(); } }); if (dlg.exec() == QDialog::Accepted) { // 信号已在内部触发,主窗口已创建并显示 return a.exec(); }优点:灵活性高,窗口之间通过信号通信,耦合度低。适合复杂的多窗口应用。缺点:需要小心管理内存,确保窗口被正确销毁(使用WA_DeleteOnClose是个好习惯)。
方案三:使用对话框的done()或accept()返回值传递数据如果只需要传递简单的数据(如用户名),可以在登录对话框关闭前,通过成员函数获取数据。
// LoginDialog.h public: QString getLoggedInUsername() const { return m_loggedInUsername; } // 提供一个获取接口 private: QString m_loggedInUsername; // LoginDialog.cpp void LoginDialog::on_pushButtonLogin_clicked() { // ... 验证逻辑 ... if (loginSuccess) { m_loggedInUsername = username; // 保存用户名 saveSettings(); accept(); } } // main.cpp LoginDialog dlg; if (dlg.exec() == QDialog::Accepted) { QString username = dlg.getLoggedInUsername(); MainWindow mainWindow; mainWindow.setWindowTitle("欢迎," + username); mainWindow.show(); return a.exec(); }优点:简单直观,适合少量数据的传递。缺点:对话框对象在exec()后仍然存在,直到main函数结束,数据获取时机明确。
我的经验选择:对于中小型项目,我更喜欢方案一的变体:将主窗口的创建放在栈上,但通过一个**应用程序单例类(AppController)**来管理状态和跳转逻辑。这个控制器在main函数中创建,它拥有登录窗口和主窗口的指针或引用,负责响应登录成功的信号,创建并显示主窗口,同时销毁登录窗口。这样既保持了生命周期的清晰,又实现了逻辑的集中管理。
4.3 子窗口的模态与非模态
从主窗口再打开其他窗口(如设置窗口、关于窗口)时,你需要决定它是模态的还是非模态的。
- 模态窗口:
QDialog::exec()。它会阻塞父窗口,用户必须处理完这个对话框才能返回主窗口。适用于必须立即处理的任务,如“保存文件?”确认框、关键设置。SettingsDialog dlg(this); // this指定父窗口 if (dlg.exec() == QDialog::Accepted) { // 用户点击了OK,应用设置 applySettings(dlg.getSettings()); } - 非模态窗口:
QWidget::show()。它和父窗口独立,用户可以同时与它们交互。适用于工具窗口、查找/替换面板等。// 注意:要防止重复打开多个同样的窗口 if (!m_settingsDialog) { // m_settingsDialog是成员变量指针 m_settingsDialog = new SettingsDialog(this); m_settingsDialog->setAttribute(Qt::WA_DeleteOnClose); // 关闭时删除 connect(m_settingsDialog, &QDialog::finished, this, [this](){ m_settingsDialog = nullptr; // 对话框关闭后,将指针置空 }); } m_settingsDialog->show(); m_settingsDialog->raise(); // 提到最前 m_settingsDialog->activateWindow(); // 激活窗口
内存管理要点:对于非模态窗口,务必注意内存泄漏。将其父指针设置为this(主窗口),或者使用WA_DeleteOnClose属性,并妥善管理成员变量指针,在窗口关闭后将其置为nullptr。
5. 项目构建、调试与发布实战
代码写完了,怎么把它变成可运行的程序,最后分发给别人用?
5.1 使用Qt Creator进行构建与调试
在Qt Creator中,左下角有构建套件选择(Debug/Release)和运行按钮。
- Debug模式:会包含完整的调试符号,代码运行速度较慢,但你可以设置断点、单步执行、查看变量值。这是开发阶段的主力。
- Release模式:编译器会进行大量优化,去除调试信息,生成体积更小、运行更快的可执行文件。用于最终发布。
调试技巧:
- 断点:在代码行号左侧点击,设置断点。程序运行到该行会暂停。
- 监视窗口:在暂停时,可以将变量拖到监视窗口,或者右键变量选择“添加到监视表达式”,实时查看其值。
- 调用栈:当程序崩溃或停在断点时,调用栈窗口显示了当前函数是如何被一层层调用的,对于定位问题根源极其有用。
- Qt特有的调试:如果遇到界面显示异常、信号槽不触发等问题,可以查看Qt Creator的“应用程序输出”面板,Qt本身会输出很多有用的警告(
qWarning)和调试信息(qDebug())。
5.2 发布可独立运行的应用程序
在Windows上,直接双击在Qt Creator中编译生成的.exe文件很可能会报错,提示缺少Qt5Core.dll之类的DLL。这是因为你的程序动态链接了Qt库。你需要将这些依赖的DLL和你的exe一起打包。
手动发布步骤(以Windows MSVC为例):
- 在Qt Creator中,将构建模式切换到Release,然后构建项目。
- 找到生成的
.exe文件(通常在build-项目名-Release/release目录下)。 - 打开Qt安装目录下的编译器对应文件夹,例如
D:\Qt\6.5.0\msvc2019_64\bin。 - 将以下必要的DLL复制到你的
.exe文件所在目录:Qt6Core.dllQt6Gui.dllQt6Widgets.dll- 根据你的项目,可能还需要
Qt6Network.dll,Qt6Sql.dll等。 - 同时还需要编译器运行库,如
MSVCP140.dll,VCRUNTIME140.dll等(它们可能在C:\Windows\System32或VC++ Redistributable目录里)。更简单的方法是,将bin目录下的*.dll都复制过去,但这样体积会很大。
- 还需要平台插件。在exe同级目录下创建
platforms文件夹,从Qt安装目录/plugins/platforms/中复制qwindows.dll(对于Windows)进去。 - 如果使用了图片等资源,并且是通过Qt的资源系统(
.qrc文件)引入的,这些资源已编译进二进制文件,无需额外处理。如果是外部文件,则需要手动复制。
自动化发布工具:手动复制非常繁琐且易错。Qt提供了两个官方工具:
windeployqt(Windows):这是最常用的。在Qt安装目录的bin文件夹下找到它。在命令行中,切换到你的exe所在目录,然后执行:
这个命令会自动分析你的exe依赖了哪些Qt模块,并把对应的DLL、插件、翻译文件等复制到当前目录。但它不复制VC++运行库,你需要确保目标机器已安装对应的Visual C++ Redistributable。D:\Qt\6.5.0\msvc2019_64\bin\windeployqt.exe --release 你的程序名.exemacdeployqt(macOS) /linuxdeployqt(Linux):作用类似。
终极简化:静态编译如果你希望生成一个完全独立、不依赖任何外部DLL的单个exe文件,可以考虑静态编译Qt库。但这非常复杂:你需要从源码编译Qt,配置静态编译选项,并且要严格遵守Qt的LGPL协议(如果你用的是开源版)。对于大多数情况,使用动态链接+部署工具是更标准的选择。
5.3 常见编译与运行问题排查
- “无法找到xxx.dll”:这是最常见的发布问题。严格按照上述部署步骤,使用
windeployqt工具,并确保VC++运行库已安装。 - 程序崩溃,提示“纯虚函数调用”:这通常发生在对象的生命周期管理出错时。比如,在一个槽函数中,
sender()对象可能已经被删除。确保信号发射时,接收对象仍然存活。使用QPointer智能指针可以帮助管理QObject的生命周期。 - 界面布局混乱:检查是否对所有容器控件正确使用了布局管理器(Layout)。在Designer中,经常使用“垂直布局”、“水平布局”、“栅格布局”,并合理设置控件的SizePolicy(大小策略)和拉伸因子(stretch)。
- 信号槽不触发:
- 检查
connect语句是否成功执行。connect函数会返回一个QMetaObject::Connection对象,你可以检查它(尽管通常不需要)。 - 确保发送者和接收者都是
QObject的派生类,并且类声明中有Q_OBJECT宏。 - 确保信号和槽的签名(参数类型)完全匹配。从Qt5开始,推荐使用新式语法
connect(sender, &Sender::signal, receiver, &Receiver::slot),它能在编译期检查类型。 - 如果使用lambda表达式作为槽,注意捕获列表中的变量生命周期。
- 检查
- 中文乱码:Qt内部使用UTF-8编码。确保你的源代码文件保存为UTF-8 with BOM(在Windows上)或UTF-8。对于字符串字面量,可以使用
QStringLiteral(u"中文")或tr(u"中文")。在main函数开头设置编码:#include <QTextCodec> int main(...) { QApplication a(...); // Qt5/6 推荐方式:默认就是UTF-8,通常无需设置。如果仍有问题: QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); // ... }
这个从登录到跳转的完整项目,虽然基础,但涵盖了Qt桌面开发的核心脉络。把这里的每一个环节吃透,尤其是窗口生命周期管理和信号槽的灵活运用,你就能应对大多数桌面应用的功能开发了。剩下的,就是根据具体业务需求,去填充更多的界面和逻辑。编程就像搭积木,掌握了基本模块的连接方式,再复杂的结构也能一步步构建起来。
