【基于Qt5仿音乐播放器(六)】——细节打磨
- 1. 设置主窗口图案
- 2. 窗口最小化
- 3. BtForm偶尔窗口乱移问题
- 4. 添加系统托盘
- 5. 保证程序只运行一次
- 5.1 使用 QLockFile 实现单实例
- 5.2 使用 QSharedMemory 实现单实例
- 5.3 两种方式对比
- 6. 禁止qDebug()输出
- 7. 项目打包
- 7.1 使用 windeployqt 部署依赖库
- 8. 总结
1. 设置主窗口图案
// widget.cpp中新增voidWidget::initUi(){/....setWindowIcon(QIcon(":/images/tubiao.png"));}2. 窗口最小化
由于窗口中控件并非全部基于Widget布局,有些控件的位置是计算死得,窗口最大化时有些控件可能无法适配尺寸,因此这里不做窗口最大化。
// widget.cpp中新增voidWidget::on_min_clicked(){this->showMinimized();}voidWidget::on_skin_clicked(){QMessageBox::information(this,"温馨提⽰","⼩哥哥正在加班紧急⽀持中...");}voidWidget::on_max_clicked(){QMessageBox::information(this,"温馨提⽰","⼩哥哥正在加班紧急⽀持中...");}3. BtForm偶尔窗口乱移问题
当我们鼠标长按在BtForm的时候,一旦移动鼠标就会发现窗口会进行偏移。这是因为子窗口事件不能传递给父窗口事件,例如在WIdget放置了一个自定义的窗口childWIdget,假如这两个窗口都设置了mousePressEvent事件,这个时候如果在chilWIdget中触发这个事件他不会传递给父窗口WIdget中。而我们代码中的在widget中的mousePressEvent就不会进行触发,只会触发BtForm中的mousePressEvent,而widge中的dragPosition记录了鼠标点击时的坐标,于是当我们点击BtForm,由于widget中的mousePressEvent并没有触发,导致dragPosition记录的还是上一次鼠标点击的坐标,所以发生了窗口闪现的偏移。而解决方法也很简单,我们只需要让鼠标放在BtForm移动鼠标时不能进行窗口移动即可,只需要多添加一个bool ismove类型的成员变量,当我们点击在btForm以外时isMove为true可移动,当我们鼠标在btForm时,isMove为false不能移动。
voidWidget::mousePressEvent(QMouseEvent*event){isMove=true;//...}voidWidget::mouseMoveEvent(QMouseEvent*event){if(event->buttons()==Qt::LeftButton&&isMove){// 鼠标开始移动出,窗口进行调整,不变值就是,鼠标相对于窗口左上角坐标的差值dragPositionthis->move(event->globalPos()-dragPosition);}}voidWidget::onBtFormClick(intid){isMove=false;//...}4. 添加系统托盘
当点击关闭按钮时,不让播放器直接退出,而是将窗口隐藏掉,窗⼝的图标在系统托盘位置,当在播放器图标上右键单击时,弹出菜单让用户选择是显示窗口还是继续退出。
#include<QSystemTrayIcon>voidWidget::initUi(){QSystemTrayIcon*trayIcon=newQSystemTrayIcon(this);trayIcon->setIcon(QIcon(":/images/tubiao.png"));QMenu*trayMenu=newQMenu(this);trayMenu->addAction("打开",this,&QWidget::showNormal);trayMenu->addSeparator();trayMenu->addAction("退出",this,&Widget::quit);trayIcon->setContextMenu(trayMenu);trayIcon->show();// .....}voidWidget::on_quit_clicked(){this->hide();}voidWidget::quit(){// 写入数据musicList.writeToDB();// 关闭数据库sqlite.close();// 退出this->close();}5. 保证程序只运行一次
现在每点击⼀次QQMusic.exe,都会创建⼀个QQMusic的实例,即同⼀个机器上可运行多份程序实例,多个实例同时运行有以下缺陷:
- 多个实例同时运行可能会导致资源浪费,如内存、CPU效率等
- 如果应用程序涉及对共享数据的修改,多个程序同时运行可能会导致数据不⼀致问题
- 若多个实例尝试访问同⼀资源时,如文件、数据库等,可能会导致冲突或错误
- 另外,用户体验不是很好,多个实例操作时容易混淆
因此有时禁止程序多开,即⼀个应用程序只能运行⼀个实例,也称为单实例应⽤程序或单例应用程序。
在Qt中,禁止程序多开的方式有好几种,常用的有QLockFile和QSharedMemory两种,下面分别介绍。
5.1 使用 QLockFile 实现单实例
QLockFile是 Qt 提供的基于文件锁的类,通过尝试锁定一个特定的锁文件来判断程序是否已经在运行。如果锁文件已被其他进程锁定,说明程序已经在运行。
// main.cpp 中使用 QLockFile 实现单实例#include<QApplication>#include<QLockFile>#include<QMessageBox>#include<QDir>#include<QStandardPaths>intmain(intargc,char*argv[]){QApplicationa(argc,argv);// 获取系统临时目录,并创建锁文件路径QString lockFilePath=QDir::tempPath()+"/QQMusic.lock";QLockFilelockFile(lockFilePath);// 尝试锁定锁文件,如果失败说明程序已经在运行if(!lockFile.tryLock(100)){QMessageBox::information(nullptr,"QQMusic","QQMusic已经在运⾏...");return0;}Widget w;w.show();returna.exec();}说明:
QDir::tempPath()获取系统临时目录,锁文件放在这里可以避免权限问题。tryLock(100)尝试锁定文件,参数 100 表示最多等待 100 毫秒,超时则返回false。- 当程序正常退出时,
QLockFile对象析构会自动释放锁;如果程序异常崩溃,锁文件可能会残留,但QLockFile会自动检测并处理过期锁。
5.2 使用 QSharedMemory 实现单实例
QSharedMemory是 Qt 提供的共享内存类,是进程间通信的一种机制。由于相同 key 值的共享内存只能存在一份,因此在程序启动时可以检测共享内存是否已经被创建,如果已经创建则说明程序已经在运行,否则程序还没有运行。
// main.cpp 中使用 QSharedMemory 实现单实例#include<QApplication>#include<QSharedMemory>#include<QMessageBox>intmain(intargc,char*argv[]){QApplicationa(argc,argv);// 实现只能实例化一个实例QSharedMemoryshareMemory("QQMusic");// 如果共享内存已经被占用了,说明已经有实例存在了if(shareMemory.attach()){QMessageBox::information(nullptr,"QQMusic","QQMusic已经在运⾏...");return0;}// 没有就创建一个shareMemory.create(1);Widget w;w.show();returna.exec();}说明:
attach()尝试连接到已存在的共享内存,如果成功说明程序已经在运行。create(1)创建大小为 1 字节的共享内存,作为程序正在运行的标记。- 程序退出时,
QSharedMemory对象析构会自动释放共享内存。
5.3 两种方式对比
| 对比项 | QLockFile | QSharedMemory |
|---|---|---|
| 实现原理 | 基于文件锁 | 基于共享内存 |
| 跨平台性 | 良好 | 良好 |
| 异常崩溃处理 | 自动检测过期锁 | 系统自动回收 |
| 代码复杂度 | 较低 | 较低 |
| 适用场景 | 简单单实例控制 | 需要进程间通信时更灵活 |
两种方式都能很好地实现单实例运行,你可以根据项目需求选择其中一种。推荐使用QLockFile,因为它更简单且对异常崩溃的处理更完善。
6. 禁止qDebug()输出
要逐个删除程序中qDebug的打印太⿇烦,可以再配置⽂件中通过添加以下语句,禁止qDebug输出:
#banqDebug outputDEFINES+=QT_NO_DEBUG_OUTPUT7. 项目打包
程序开发完成后,需要将其打包成可独立运行的 exe 文件,方便在没有安装 Qt 开发环境的机器上运行。下面以 Windows 平台为例,介绍基于 Qt5 的打包流程。
7.1 使用 windeployqt 部署依赖库
Qt 程序在发布时,需要将程序依赖的 Qt 动态库(如 Qt5Core.dll、Qt5Gui.dll、Qt5Widgets.dll 等)一起打包。windeployqt是 Qt 官方提供的部署工具,可以自动扫描 exe 依赖的 Qt 库并复制到指定目录。
第一步:编译 Release 版本
在 Qt Creator 中,将构建模式切换为Release,然后重新编译项目,生成 release 版本的QQMusic.exe。
第二步:创建发布目录并复制 exe
将新建⼀个⽂件,将release模式可执行程序拷贝到这个新目录里面
第三步:运行 windeployqt 部署依赖库
进入新建的文件夹,按shift+鼠标右击打开powershell
执行完成后,windeployqt ./QQMusic.exe会自动将程序运行所需的 Qt 动态库、插件(如 platforms、imageformats 等)复制到发布目录中。
第四步:补充非 Qt 依赖
如果程序还依赖其他第三方库(如 sqlite3.dll、ffmpeg.dll 等),需要手动将这些动态库复制到发布目录中:
copy D:\QtProject\3rdparty\sqlite3.dll D:\QQMusicRelease\注意事项:
windeployqt需要与编译时使用的 Qt 版本和编译器一致,否则可能部署错误的库。- 部署完成后,建议在没有安装 Qt 的干净机器上测试运行,确保所有依赖都已包含。
- 如果程序使用了 Qt 的某些模块(如 Qt5Sql、Qt5Network),
windeployqt会自动检测并部署对应的库。