
在桌面端轻量化影音工具开发场景中,Qt凭借其跨平台、高性能、界面灵活定制的优势,成为开发者首选框架之一。相比于臃肿的第三方音乐软件,自主开发的本地音乐播放器无广告、无后台推送、启动速度快,仅专注本地音频播放核心功能,非常适合日常学习使用和Qt项目实战练习。
本文将基于Qt5 + QMediaPlayer + QMediaPlaylist实现一款功能完整的轻量化本地音乐播放器,涵盖音频播放控制、播放列表管理、多模式循环播放、系统托盘、配置文件持久化、窗口拦截关闭等核心功能。同时结合完整源码,逐模块讲解实现思路、核心逻辑、踩坑问题和优化方案,适合Qt初学者入门实战,也可作为课程设计、小型桌面项目参考案例。
一、项目整体功能概述
本音乐播放器为纯本地音频播放工具,不依赖网络资源,支持主流mp3、wav、wma等音频格式,整合了市面上主流播放器的基础核心功能,整体功能完备且轻量化,具体功能清单如下:
-
基础播放控制:支持播放、暂停、停止、上一曲、下一曲无缝切换,播放状态实时同步UI按钮状态;
-
进度与音量调节:可拖拽进度条跳转播放位置,实时显示当前播放时间/总时长,支持音量滑动调节、一键静音切换;
-
播放列表管理:批量添加本地音频、删除单曲、清空全部列表,双击列表曲目快速切换播放;
-
多播放模式切换:实现顺序播放、随机播放、单曲循环三种模式自由切换,模式状态持久化保存;
-
数据持久化存储:通过INI配置文件保存播放列表、当前播放索引、播放模式,重启软件自动恢复上次播放状态;
-
系统托盘功能:最小化后台运行,托盘右键支持显示主窗口、一键退出程序;
-
人性化交互优化:关闭窗口二次确认、音频异常报错提示、空列表播放弹窗提示、中文乱码修复等细节优化。
二、核心技术栈介绍
本项目基于Qt5框架开发,核心依赖Qt多媒体模块,无需引入第三方库,原生API即可实现全部功能,兼容性极强:
-
QMediaPlayer:Qt原生多媒体播放核心类,负责音频解码、播放、暂停、停止、音量控制、播放状态监听,是播放器的核心载体;
-
QMediaPlaylist:播放列表管理类,专门用于管理多音频文件队列,支持索引切换、播放模式设置、媒体资源批量添加与删除;
-
QSettings:配置文件读写类,负责INI文件的解析与写入,实现项目数据持久化;
-
QSystemTrayIcon:系统托盘类,实现窗口后台驻留、托盘菜单自定义,提升软件使用体验;
-
QFileDialog/QFileInfo:文件选择与信息解析类,用于批量读取本地音频文件、解析文件名与文件路径。
三、核心代码模块详细解析
本项目代码结构清晰,分为主函数托盘初始化、主窗口构造初始化、播放核心逻辑、列表管理、配置读写、事件拦截六大模块,下面逐一拆解核心实现逻辑。
3.1 主函数:系统托盘与程序初始化
main函数主要负责程序启动初始化、主窗口创建、系统托盘功能搭建,是程序的入口核心。区别于普通Qt窗口项目,本项目新增了系统托盘常驻功能,实现软件最小化后台运行。
代码中通过QSystemTrayIcon创建托盘图标,搭配QMenu自定义右键菜单,实现"显示主窗口"和"退出程序"两大核心操作。通过信号槽绑定菜单点击事件,窗口最小化后可通过托盘快速唤起,关闭程序时自动保存配置,保证数据不丢失。同时移除了冗余的调试窗口标题,保证软件界面整洁。
托盘功能的核心优化点在于窗口状态判断,通过检测窗口是否为最小化状态,精准恢复窗口大小并置顶激活,解决了传统托盘唤起窗口层级过低、无法前置的问题。
3.2 主窗口构造:全局初始化配置
主窗口构造函数是整个播放器的初始化核心,涵盖控件初始化、播放器对象创建、信号槽绑定、配置读取、列表恢复等全流程操作。
首先初始化全局变量,定义当前播放索引、默认播放模式,固定窗口初始尺寸。随后创建QMediaPlayer和QMediaPlaylist对象,并将播放列表绑定到播放器,实现播放器对列表资源的统一调度管理。
信号槽绑定是本模块的核心,通过绑定播放器状态、播放进度、音频时长、播放错误、列表索引变更等信号,实时监听播放器状态变化,同步更新UI界面和播放逻辑。相比轮询检测,Qt信号槽机制属于事件驱动,资源占用更低、响应更及时。
最后读取本地INI配置文件,恢复上次保存的播放列表、播放索引和播放模式,实现软件重启无感知续播,极大提升了用户体验。
3.3 播放核心逻辑:状态与进度同步
播放状态同步是保证UI与功能一致性的关键。通过onStateChanged槽函数监听播放器播放状态,动态切换播放、暂停、停止按钮的可用状态:播放时禁用播放按钮、启用暂停和停止按钮,暂停/停止时恢复播放按钮可用状态,完全贴合主流播放器交互逻辑。
进度与时间更新模块实现了播放进度实时同步。onDurationChanged获取音频总时长,初始化进度条最大值和总时间文本;onPositionChanged实时监听播放位置,更新进度条和已播放时间。同时增加滑块拖拽判定逻辑,当用户手动拖动进度条时,暂停自动刷新,避免进度条抖动、跳转失灵的问题,这是播放器交互优化的核心细节。
针对时间显示格式,代码通过整除、取余运算换算分秒,后续可结合格式化补零,实现标准的00:00时间展示,解决单数字时间显示不规整的问题。
3.4 播放列表管理模块
播放列表是本地播放器的核心功能载体,本项目实现了批量添加、删除单曲、清空列表、双击切歌全功能逻辑。
批量添加音频功能通过QFileDialog弹出文件选择框,筛选mp3、wav、wma主流音频格式,支持多选文件批量导入。通过QFileInfo解析文件名称,去除路径和后缀,仅展示纯净歌名,同时将完整文件路径存入全局容器和播放列表,兼顾界面美观和播放功能。
删除功能做了边界逻辑优化:精准获取当前选中列表行,删除对应数据,同时判断删除曲目是否为正在播放的曲目。如果删除当前播放曲目,自动切换到上一曲,无曲目时停止播放并重置界面提示,避免程序闪退、播放逻辑错乱的问题。
清空列表功能会清空容器数据、播放列表和UI列表,停止播放并重置状态,保证数据完全同步,无残留冗余数据。同时支持列表双击快速切歌,无需点击按钮,操作更便捷。
3.5 播放模式切换逻辑
项目实现了顺序播放、随机播放、单曲循环 三种主流播放模式,通过全局变量m_iLoopMode记录当前模式,点击模式按钮循环切换。
updateLoopUi函数为模式更新核心,根据模式变量匹配QMediaPlaylist原生播放模式,同步修改按钮图标和文字,实现视觉与功能的统一。模式切换后自动保存到配置文件,重启软件无需重新设置。
三种模式完美适配Qt原生播放逻辑:顺序播放按列表索引依次播放、随机播放打乱播放顺序、单曲循环重复播放当前曲目,覆盖日常播放全部场景。
3.6 配置文件持久化
数据持久化是桌面软件的必备功能,本项目通过QSettings读写INI配置文件,实现播放数据永久保存。核心逻辑分为读取配置和写入配置两部分。
读取配置时,打开程序同级目录下的config.ini文件,读取曲目数量、当前播放索引、播放模式,循环读取所有音频路径并恢复播放列表,实现重启续播。同时增加边界保护,避免配置数据异常导致程序崩溃。
写入配置时,先清空原有配置分组,清除冗余垃圾数据,再重新写入最新的列表数量、播放索引、播放模式和所有音频路径,保证配置文件整洁、数据精准,避免旧数据残留导致的功能异常。
3.7 事件拦截与异常处理
为优化软件容错率和使用体验,项目增加了多重异常拦截和提示机制。
重写closeEvent关闭事件,拦截窗口默认关闭行为,弹出确认弹窗,防止用户误触关闭软件。确认关闭后自动停止播放、保存最新配置,再关闭窗口;取消则忽略关闭事件,继续运行程序。
新增音频异常捕获槽函数,当音频文件损坏、路径失效、格式不支持时,主动捕获播放器错误信号,弹出异常提示弹窗,告知用户播放失败原因,避免程序闪退。同时对空列表播放、无选中删除等异常场景做了弹窗提示,交互更人性化。
四、项目优化亮点与问题解决
4.1 中文乱码解决方案
Windows平台MSVC编译器默认编码为GBK,Qt项目极易出现中文乱码问题。本项目通过添加编译指令#pragma execution_character_set("utf-8"),强制源码中文字符以UTF-8编码编译,彻底解决弹窗提示、按钮文字、配置文件中文乱码问题,兼容性极强。
4.2 进度条拖拽防抖优化
原生播放器进度条拖拽时会频繁刷新位置,导致界面抖动、跳转不准确。项目通过判断滑块拖拽状态,用户手动拖动时暂停自动进度刷新,松开后正常同步,完美解决进度条抖动问题。
4.3 托盘后台驻留优化
区别于简易托盘实现,本项目托盘唤起窗口时,不仅显示窗口,还会激活窗口、提升窗口层级,保证窗口置顶显示,解决了托盘唤起窗口隐藏在其他窗口下方的问题,交互更贴合商业软件逻辑。
4.4 数据冗余清理优化
配置文件写入时,先删除原有APPCFG分组,再重新写入数据,彻底避免多次增删曲目后,配置文件残留废弃key导致的读取异常、列表数据错乱问题。
五、项目可拓展方向
本项目已完成基础播放器全部核心功能,代码结构规范、拓展性极强,可根据需求进一步迭代优化,适合二次开发:
-
UI界面美化:添加QSS样式表,统一按钮、列表、进度条样式,增大列表行高、优化滑块尺寸、自定义选中高亮颜色,解决原生控件简陋、交互局促的问题;
-
歌词显示功能:解析LRC歌词文件,实现歌词同步滚动、精准匹配播放进度;
-
快捷键控制:绑定键盘快捷键,实现空格暂停/播放、上下键调节音量等快捷操作;
-
皮肤主题切换:搭建明暗两套主题,实现一键切换界面配色;
-
音频均衡器:基于Qt多媒体模块拓展音效调节功能,支持高低音调节。
六、项目总结
本文实现的Qt轻量化本地音乐播放器,代码结构清晰、功能完整、交互人性化,完美解决了基础音频播放、列表管理、数据持久化、后台驻留等核心需求。项目全程使用Qt原生API开发,无第三方依赖,编译运行简单、跨平台性强,非常适合Qt初学者练手、课程设计、小型桌面软件开发参考。
通过本项目开发,能够熟练掌握Qt信号槽机制、多媒体模块使用、配置文件读写、窗口事件拦截、系统托盘开发、异常处理等核心知识点,同时积累桌面软件交互优化、代码容错处理的实战经验。项目源码规范、注释清晰,可直接运行使用,也可在此基础上持续迭代拓展更多个性化功能。

cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QMediaPlayer>
#include <QMediaPlaylist>
#include <QPushButton>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
QMediaPlayer *player; // 媒体播放器对象
QMediaPlaylist *playlist; // 播放列表管理
QString durationTime; // 音频总时长文本
QString positionTime; // 当前播放位置时间文本
void updateLoopUi(); // 更新播放模式UI(图标+文字)
void ReadConfig(); // 读取ini配置文件
void WriteConfig(); // 保存配置到ini
int m_iCurrentIndex; // 记录上次播放索引
int m_iLoopMode; // 播放模式:0顺序 1随机 2单曲循环
QStringList m_list; // 保存全部音频文件路径
virtual void closeEvent(QCloseEvent *); // 重写窗口关闭事件
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
// 播放器状态变更槽
void onStateChanged(QMediaPlayer::State state);
// 播放列表索引切换
void onPlaylistChanged(int position);
// 获取音频总时长
void onDurationChanged(qint64 duration);
// 播放位置实时更新
void onPositionChanged(qint64 position);
// 添加音频文件
void on_btnAdd_clicked();
// 播放
void on_btnPlay_clicked();
// 暂停
void on_btnPause_clicked();
// 停止
void on_btnStop_clicked();
// 双击列表项播放
void on_listWidget_doubleClicked(const QModelIndex &index);
// 清空播放列表
void on_btnClear_clicked();
// 调节音量滑块
void on_sliderVolumn_valueChanged(int value);
// 静音按钮
void on_btnSound_clicked();
// 播放进度拖动跳转
void on_sliderPosition_valueChanged(int value);
// 删除选中音频
void on_btnRemove_clicked();
// 上一曲
void on_btnPrevious_clicked();
// 下一曲
void on_btnNext_clicked();
// 切换播放模式
void on_btnLoop_clicked();
// 播放器出错回调
void onMediaError();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFileDialog>
#include <QSettings>
#include <QMessageBox>
#include <QCloseEvent>
// MSVC编译器:源码中文字符串编译为UTF‑8,解决Windows中文乱码
#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_iCurrentIndex = 0;
m_iLoopMode = 0;
resize(359, 512); // 设置窗口初始大小
player = new QMediaPlayer(this);
playlist = new QMediaPlaylist(this);
player->setPlaylist(playlist);
// 信号‑槽绑定
connect(player, SIGNAL(stateChanged(QMediaPlayer::State)),
this, SLOT(onStateChanged(QMediaPlayer::State)));
connect(player, SIGNAL(positionChanged(qint64)),
this, SLOT(onPositionChanged(qint64)));
connect(player, SIGNAL(durationChanged(qint64)),
this, SLOT(onDurationChanged(qint64)));
connect(player, SIGNAL(error(QMediaPlayer::Error)), this, SLOT(onMediaError()));
connect(playlist, SIGNAL(currentIndexChanged(int)),
this, SLOT(onPlaylistChanged(int)));
ReadConfig(); // 读取ini配置
updateLoopUi(); // 初始化播放模式UI
// 加载配置里保存的音频列表
for(int i = 0; i < m_list.size(); i++)
{
QString filePath = m_list.at(i);
QFileInfo fileInfo(filePath);
QString fileName = fileInfo.completeBaseName();
ui->listWidget->addItem(fileName);
playlist->addMedia(QUrl::fromLocalFile(filePath));
}
// 恢复上次播放的曲目并自动播放
if(m_list.size() > 0)
{
if(m_iCurrentIndex < 0 || m_iCurrentIndex >= m_list.size())
m_iCurrentIndex = 0;
playlist->setCurrentIndex(m_iCurrentIndex);
player->play();
}
}
MainWindow::~MainWindow()
{
delete ui;
}
// 根据播放器状态更新按钮可用状态
void MainWindow::onStateChanged(QMediaPlayer::State state)
{
ui->btnPlay->setEnabled(!(state == QMediaPlayer::PlayingState));
ui->btnPause->setEnabled(state == QMediaPlayer::PlayingState);
ui->btnStop->setEnabled(state == QMediaPlayer::PlayingState);
}
// 播放索引切换,同步更新列表选中行与标题显示
void MainWindow::onPlaylistChanged(int position)
{
ui->listWidget->setCurrentRow(position);
QListWidgetItem *item = ui->listWidget->currentItem();
if (item)
ui->LabCurMedia->setText(item->text());
}
// 获取音频总时长,更新进度条最大值与总时间文本
void MainWindow::onDurationChanged(qint64 duration)
{
ui->sliderPosition->setMaximum(duration);
int secs = duration / 1000;
int mins = secs / 60;
secs = secs % 60;
durationTime = QString::asprintf("%d:%d", mins, secs);
ui->LabRatio->setText(positionTime + "/" + durationTime);
}
// 当前播放位置变化,更新进度条与已播放时间文本
void MainWindow::onPositionChanged(qint64 position)
{
if (ui->sliderPosition->isSliderDown()) // 正在手动拖动滑块,不回写
return;
ui->sliderPosition->setSliderPosition(position);
int secs = position / 1000;
int mins = secs / 60;
secs = secs % 60;
positionTime = QString::asprintf("%d:%d", mins, secs);
ui->LabRatio->setText(positionTime + "/" + durationTime);
}
// 添加音频文件
void MainWindow::on_btnAdd_clicked()
{
QString curPath = QDir::homePath();
QString dlgTitle = "选择音频文件";
QString filter = "音频文件(*.mp3 *.wav *.wma);;mp3文件(*.mp3);;wav文件(*.wav);;wma文件(*.wma);;所有文件(*.*)";
QStringList fileList = QFileDialog::getOpenFileNames(this, dlgTitle, curPath, filter);
if (fileList.count() < 1)
return;
for (int i = 0; i < fileList.count(); i++)
{
QString filePath = fileList.at(i);
playlist->addMedia(QUrl::fromLocalFile(filePath));
QFileInfo fileInfo(filePath);
ui->listWidget->addItem(fileInfo.completeBaseName());
m_list.append(filePath);
}
WriteConfig(); // 更新配置文件
}
// 播放按钮
void MainWindow::on_btnPlay_clicked()
{
if(playlist->mediaCount() <= 0)
{
QMessageBox::information(this, "提示", "请先添加音频文件");
return;
}
if (playlist->currentIndex() < 0)
playlist->setCurrentIndex(0);
player->play();
}
// 暂停
void MainWindow::on_btnPause_clicked()
{
player->pause();
}
// 停止播放
void MainWindow::on_btnStop_clicked()
{
player->stop();
}
// 双击列表项切换播放
void MainWindow::on_listWidget_doubleClicked(const QModelIndex &index)
{
int rowNo = index.row();
playlist->setCurrentIndex(rowNo);
player->play();
}
// 清空全部播放列表
void MainWindow::on_btnClear_clicked()
{
m_list.clear();
playlist->clear();
ui->listWidget->clear();
player->stop();
WriteConfig();
}
// 音量滑块调节
void MainWindow::on_sliderVolumn_valueChanged(int value)
{
player->setVolume(value);
}
// 静音切换
void MainWindow::on_btnSound_clicked()
{
bool mute = player->isMuted();
player->setMuted(!mute);
if (mute)
ui->btnSound->setIcon(QIcon(":/images/images/volumn.bmp"));
else
ui->btnSound->setIcon(QIcon(":/images/images/mute.bmp"));
}
// 进度条手动跳转播放位置
void MainWindow::on_sliderPosition_valueChanged(int value)
{
player->setPosition(value);
}
// 删除选中的音频项
void MainWindow::on_btnRemove_clicked()
{
int pos = ui->listWidget->currentRow();
if(pos < 0) return;
m_list.removeAt(pos);
QListWidgetItem *item = ui->listWidget->takeItem(pos);
delete item;
// 如果删除的是正在播放的曲目
if (playlist->currentIndex() == pos)
{
int nextPos = 0;
if (pos >= 1)
nextPos = pos - 1;
playlist->removeMedia(pos);
if (ui->listWidget->count() > 0)
{
playlist->setCurrentIndex(nextPos);
}
else
{
player->stop();
ui->LabCurMedia->setText("无曲目");
}
}
else
{
playlist->removeMedia(pos);
}
WriteConfig();
}
// 上一曲
void MainWindow::on_btnPrevious_clicked()
{
playlist->previous();
}
// 下一曲
void MainWindow::on_btnNext_clicked()
{
playlist->next();
}
// 读取config.ini配置
void MainWindow::ReadConfig()
{
QString sPath = qApp->applicationDirPath();
QSettings settings(sPath + QString("/config.ini"), QSettings::IniFormat, this);
settings.setIniCodec("UTF-8");
int iCount = settings.value("/APPCFG/ItemCount").toInt();
m_iCurrentIndex = settings.value("/APPCFG/CurrentIndex").toInt();
m_iLoopMode = settings.value("/APPCFG/LoopMode").toInt();
// 边界保护
if (m_iLoopMode < 0) m_iLoopMode = 0;
if (m_iLoopMode > 2) m_iLoopMode = 2;
// 读出保存的音频路径
for(int i = 0; i < iCount; i++)
{
QString sItem = QString("/APPCFG/Item") + QString::number(i);
QString sTemp = settings.value(sItem).toString();
m_list.append(sTemp);
}
}
// 保存配置到config.ini,先清除旧分组避免残留垃圾key
void MainWindow::WriteConfig()
{
QString sPath = qApp->applicationDirPath();
QString filePath = sPath + QString("/config.ini");
QSettings settings(filePath, QSettings::IniFormat, this);
settings.setIniCodec("UTF-8");
settings.remove("/APPCFG"); // 删除整个分组,清理旧数据
settings.setValue("/APPCFG/ItemCount", ui->listWidget->count());
settings.setValue("/APPCFG/CurrentIndex", playlist->currentIndex());
settings.setValue("/APPCFG/LoopMode", m_iLoopMode);
for(int i = 0; i < m_list.size(); i++)
{
QString sItem = QString("/APPCFG/Item") + QString::number(i);
settings.setValue(sItem, m_list.at(i));
}
}
// 拦截窗口关闭事件,弹出退出确认框
void MainWindow::closeEvent(QCloseEvent *e)
{
int ret = QMessageBox::question(this, "提示", "确认退出?", QMessageBox::Ok|QMessageBox::Cancel);
if(ret == QMessageBox::Ok)
{
player->stop();
WriteConfig();
e->accept(); // 允许关闭窗口
}
else
{
e->ignore(); // 取消关闭
}
}
// 切换播放模式:顺序/随机/单曲循环
void MainWindow::on_btnLoop_clicked()
{
m_iLoopMode++;
if(m_iLoopMode > 2)
m_iLoopMode = 0;
updateLoopUi();
WriteConfig();
}
// 更新播放模式:设置实际模式+按钮文字图标
void MainWindow::updateLoopUi()
{
if (m_iLoopMode == 0)
{
playlist->setPlaybackMode(QMediaPlaylist::Sequential);
ui->btnLoop->setIcon(QIcon(":/images/images/Sequential.bmp"));
ui->btnLoop->setText("顺序播放");
}
else if(m_iLoopMode == 1)
{
playlist->setPlaybackMode(QMediaPlaylist::Random);
ui->btnLoop->setIcon(QIcon(":/images/images/Random.bmp"));
ui->btnLoop->setText("随机播放");
}
else if(m_iLoopMode == 2)
{
playlist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop);
ui->btnLoop->setIcon(QIcon(":/images/images/CurrentItemInLoop.bmp"));
ui->btnLoop->setText("单曲循环");
}
}
// 播放器错误弹窗
void MainWindow::onMediaError()
{
QMessageBox::warning(this, "播放异常", "该音频无法播放,文件损坏或已被移除!");
}
#include "mainwindow.h"
#include <QApplication>
#include <QSystemTrayIcon>
#include <QMenu>
#include <QMessageBox>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//主窗口
MainWindow w;
w.setWindowTitle("音乐播放器260814");
// -------------------- 系统托盘功能 --------------------
// 创建托盘图标
QSystemTrayIcon trayIcon(QIcon(":/images/images/music.ico"), &a);
trayIcon.setToolTip("音乐播放器");
// 创建右键菜单
QMenu *trayMenu = new QMenu(&w);
// 添加"显示主窗口"菜单项
QAction *showAction = trayMenu->addAction("显示主窗口");
// 添加"关闭程序"菜单项
QAction *quitAction = trayMenu->addAction("退出程序");
// -------------------- 信号与槽连接 --------------------
// 点击"显示主窗口"时,恢复主窗口显示并激活
QObject::connect(showAction, &QAction::triggered, &w, [&]()
{
// 检查窗口是否处于最小化状态,如果是则恢复正常大小
if (w.windowState() & Qt::WindowMinimized)
w.setWindowState(w.windowState() & ~Qt::WindowMinimized);
w.show(); // 显示窗口(若已最小化/隐藏)
w.activateWindow(); // 激活窗口并置于前端
w.raise(); // 提升窗口层级
});
// 点击"退出程序"时,弹出确认
QObject::connect(quitAction, &QAction::triggered, &a, [&]()
{
w.close();
});
// 将菜单关联到托盘图标
trayIcon.setContextMenu(trayMenu);
// 显示托盘图标和主窗口
trayIcon.show();
w.show();
return a.exec();
}