【基于Qt5仿音乐播放器(六)】——细节打磨与总结

【基于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的实例,即同⼀个机器上可运行多份程序实例,多个实例同时运行有以下缺陷:

  1. 多个实例同时运行可能会导致资源浪费,如内存、CPU效率等
  2. 如果应用程序涉及对共享数据的修改,多个程序同时运行可能会导致数据不⼀致问题
  3. 若多个实例尝试访问同⼀资源时,如文件、数据库等,可能会导致冲突或错误
  4. 另外,用户体验不是很好,多个实例操作时容易混淆

因此有时禁止程序多开,即⼀个应用程序只能运行⼀个实例,也称为单实例应⽤程序或单例应用程序。

在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. 总结

相关推荐
信誓旦旦的程序猿3 小时前
【Python量化实战宝典 #26】止损与回撤控制手册:移动止损、最大回撤(Python 可跑)
python·1024程序员节·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
长不大的杨瑞杰6 小时前
OpenCode v2 MCP 与插件体系实战:把数据库、工单和内部 API 安全接入 Agent
人工智能·安全·语言模型·软件工程·1024程序员节
深圳市益普科技有限公司6 小时前
半导体 CIM 与 MES 的数据模型分工:工单该由谁持有
1024程序员节
乌暮6 小时前
综合案例:宠物电商系统
java·开发语言·spring·mvc·restful·1024程序员节
几何心凉7 小时前
论文解读|智元 GO-1:VLA 进化到 ViLLA,中间多出来的那个“L“到底有什么用?
机器人·1024程序员节·具身智能·启元机器人
lhdz_bj8 小时前
Mysql Server配置详解
mysql·配置·file·config·1024程序员节·option·选项
larance9 小时前
[机器学习40讲]33-序列化建模:隐马尔可夫模型.
机器学习·1024程序员节
geats人山人海9 小时前
现代c++第四章 简单类的概念
1024程序员节
ctlover9 小时前
langchain 中间件
中间件·langchain·1024程序员节