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