本文详细介绍一个利用基于对话框的MFC编制简易音乐播放器的流程及相关代码;MFC初学者可以通过这个案例增加MFC编程经验,提高编程水平。这里编程环境使用的是VS2022。
1、建立一个基于对话框的MFC项目,项目名称:simpleMusicPlayer。对话框建好后,将对话框上的“TODO:在此放置对话框控件。”、"确定"和"取消"删掉。
2、工具箱中选择【Button】按钮拖入对话框,右键按钮选择“属性“,描述文字修改为“打 开(O)”,ID修改为“IDC_OPEN”。
接着,再右键按钮选择“添加事件处理程序 ......”,类列表选择“CsimpleMusicPlayerDlg”,其余按缺省,点“确定”后在“simpleMusicPlayerDlg.cpp”中会出现一个新的函数,void CsimpleMusicPlayerDlg :: OnBnClickedOpen() { },这个函数的用途是用于打开音乐文件的,为保证程序能够顺利处理音乐文件,我们需要在simpleMusicPlayerDlg.cpp的头部包含头文件 【#include "mmsystem.h"】 和库文件【# pragma comment(lib,"winmm.lib")】,并请提前熟悉一下mciSendString。在这个函数中我们添加如下代码:
//打开音乐文件
CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY, L"All Files(*.*)||", AfxGetMainWnd(), 0UL, 1); //这里声明一个打开文件对话框类对象
dlg.DoModal(); //显示打开文件对话框对象
strOpenfile = L"open "; //公用变量strOpenfile是后边mciSendString的参数
strOpenfile.Append(dlg.GetPathName()); //GetPathName--获得路径及文件名
strOpenfile.Append(L" alias bgm"); //对播放的音乐文件起别名bgm,可使后续代码简单
mciSendString(strOpenfile, 0, 0, 0); //利用mciSendString函数将歌曲文件打开
// 说明:上面代码中出现的strOpenfile是CString型的变量。这个变量多个函数中都会用到,因此需把声明放到类声明文件simpleMusicPlayerDlg.h中,并以public方式声明;类似的变量声明后边还会遇到,本文中我们称其为公用变量(下文中凡提到公用变量,都表示是在那声明的)。
当我们单击“打 开(O)”按钮时,上述代码将被执行,此时会出现一个选择文件的对话框,完成选择后,文件名及路径将作为字符串参数的组成部分传到mciSendString函数中将文件打开。
3、文件打开后,我们希望在对话框上有所显示,为此,我们再回到对话框界面,选择添加文本编辑框放到“打开(O)”按钮的右边。右键文本编辑框选择添加变量,给该编辑框添加一个变量m_choice,这个变量代表这个编辑框进行显示音乐文件的操作。由于显示内容随“打开(O)”按钮被点击而变化,所以相关代码仍追加到“打 开(P)"按钮对应的函数void CsimpleMusicPlayerDlg::OnBnClickedOpen() { }之中,具体如下:
//在打开按钮旁文本编辑框显示音乐文件名
m_choice.SetWindowText(dlg.GetFileName()); //文本编辑框显示打开的文件名称(不含路径)
此时,如果我们试着编译运行,当我们点击“打 开(O)”按钮,选择一个音乐文件时,会发现旁边的编辑框把文件名显示出来了。
4、文件打开后,下一步的操作是播放音乐文件,为此,我们再回到对话框界面,再向对话框中拖入一个【Button】按钮,放到“打 开(O)”按钮的下方。右键该按钮,选择属性,描述文字改为“播 放(P)”,ID改为“IDC_PLAY”。接着,右键这个按钮添加事件处理程序,注意事件处理程序的类列表要选择CsimpleMusicPlayerDlg,其余按缺省。我们在simpleMusicPlayerDlg.cpp中会看到一个新的函数void CsimpleMusicPlayerDlg::OnBnClickedPlay(){};在这个函数中我们添加下列代码:
//播放音乐
CString temStrPlay;
temStrPlay.Append(L"play bgm");
temStrPlay.Append(L" repeat"); //repeat重复播放:如不需重复播放可将此语句注释掉
mciSendString(temStrPlay, 0, 0, 0);
这时,我们试着运行这个程序,在选择一个音乐文件后,点击播放,如硬件设置无误,就可以听到播放音乐的声音。
5、现在看播放器的界面显得太过简单、太空旷了,为此再增加一些内容。
6、首先增加暂停功能。回到对话框界面,继续拖入一个【Button】按钮。右键该按钮,描述文字改为“暂 停(S)”,ID改为IDC_PAUSE。右键“暂停(S)”按钮,选择添加事件处理程序,注意类列表仍要选择CsimpleMusicPlayerDlg,其余按缺省(后续相同)。此时,我们在simpleMusicPlayerDlg.cpp中,又可看到一个新的函数:void CsimpleMusicPlayerDlg::OnBnClickedPause() { };我们在这个函数中添加代码如下:
mciSendString(L"pause bgm", 0, 0, 0); //暂停音乐播放
7、接着,在增加继续播放功能。回到对话框界面,继续拖入一个【Button】按钮。右键该按钮,描述文字改为“继 续(C)”,ID改为IDC_CONTINUE。右键“继 续(C)”按钮,选择添加事件处理程序,可以看到又增加了新的函数:void CsimpleMusicPlayerDlg::OnBnClickedContinue() { };在这个函数中,添加代码如下:
mciSendString(L"resume bgm", 0, 0, 0); //继续音乐播放
8、继续增加结束播放功能。停止播放后,音乐停止并且打开音乐文件的显示及记录文件路径和文件名的变量也应清空。为此,继续拖入一个【Button】按钮。右键该按钮,描述文字改为“结 束(E)”,ID改为IDC_END。右键“结 束(E)”按钮,选择添加事件处理程序,可以看到又增加了新的函数:void CsimpleMusicPlayerDlg::OnBnClickedEnd() { };在这个函数中添加代码如下:
//结束播放
mciSendString(L"close bgm", 0, 0, 0);
strOpenfile = L"";//清空记录打开文件路径及文件名的变量
m_choice.SetWindowText(L""); //显示的文件名清空
9、为了加强播放器界面可视性,我们再增加一个进度显示。做法是:a.先拖入一个静态文本框,将其文本描述修改为“播放进度”;b.再拖入一个进度条控件,右键点击该控件选择添加变量,变量名为m_progress、ID更改为IDC_PROGRESS。由于对进度条本身没有操作,因此,我们没有必要为其添加处理程序。有关进度条如何进行显示,我们在其他程序中设置代码。
进度条的初始值设置,我们在初始化程序BOOL CMusicTestDlg::OnInitDialog()函数中增加以下代码:
m_progress.SetRange(0, 900);//进度条的显示范围
m_progress.SetPos(0);//开始播放时从0开始显示
为了正确显示播放进度,我们需要知道音乐文件的长度,获得这个长度在“打 开(O)”按钮对应的函数中最为方便。根据这个长度我们可以计算出在每个周期(PERIOD,这个周期后边讲)进度条向前走的长度,也就是步长。这个步长变量(int proFoot)也涉及多个函数,因此也要在simpleMusicPlayerDlg.h构造函数中以public方式声明。接下来我们在void CsimpleMusicPlayerDlg::OnBnClickedOpen()中在添加代码如下:
//获得音乐文件的长度并计算进度条前进步长
CString askTimeLenString;
askTimeLenString = L"status bgm length";
TCHAR timeLen[256];
mciSendString(askTimeLenString, timeLen, sizeof(timeLen) / sizeof(TCHAR), NULL);
//参1:发出的字符串指令;参2:存放返回歌曲长度(毫秒)的缓冲区名;
//参3:缓冲区长度(字节数);参4:回调窗口句柄,一般为NULL
proFoot = 900 / ((_ttoi(timeLen) / PERIOD);
至此,进度条还是不能正常工作,我们还需要设置一个系统计时器,用来周期性的调用进度条,每个周期前进一个步长,为此,在初始化程序BOOL CMusicTestDlg::OnInitDialog()函数中在增加下面代码:
#define PERIOD 200 //计时器周期为200毫秒
SetTimer(1, PERIOD, NULL);//设置计时器
系统计时器对应的事件处理程序afx_msg void OnTimer( UINT nIDEvent )不能自动生成,需要通过类向导添加,项目->类向导->选择C***Dlg类->消息->WM_TIMER-添加处理程序,在void OnTimer( UINT nIDEvent ) { }中添加:m_progress.SetPos(proFoot++);//进度条前进设置
至此,进度条已能随着音乐的节奏往前走,但有个问题当我们按下“暂 停(S)“时进度条还是接着往前走,因此进度条前进的设置应是有条件的,也就是当选择播放时进度条开始往前走,当选择暂停时进度条停止,当选择结束时进度条回到初始位置。为此在下面各函数中添加相应代码:
首先,设置一个全局变量 BOOL isShow ( TRUE 显示 FALSE 清空或暂停)
初始化函数CsimpleMusicPlayerDlg::OnInitDialog()中添加: isShow=FALSE;
播放函数CsimpleMusicPlayerDlg::OnBnClickedPlay()添加:isShow=TRUE;
暂停函数CsimpleMusicPlayerDlg::OnBnClickedPause()添加:isShow=FALSE;
继续函数CsimpleMusicPlayerDlg::OnBnClickedContinue()添加:isShow=TRUE;
结束函数CsimpleMusicPlayerDlg::OnBnClickedEnd()添加:isShow=FALSE;m_progress.SetPos(0);
系统计时器函数CsimpleMusicPlayerDlg::OnTimer(UINT_PTR nIDEvent)更改:
if ( isShow ) {m_progress.SetPos(proFoot++); }
10、音量调节
截止到第9个步骤,作为简易音乐播放器的基本功能已经具备,但调节音量需要利用操作系统的音量调节按钮,显得不太完美。在此,我们再增加一个音量调节功能。
回到对话框界面,在“播放进度”下方拖入一个静态文本框并将其属性描述文字改为“音量调节”。在其右侧,再拖入一个滑动条控件,右键其属性ID改为IDC_SLIDER。再右键滑动条,为其添加控件型变量m_slider。
音量调节我们仍然使用向mciSendString发送字符串方式。音量值范围取0-1000,通过滑动条的位置进行调整。这里就涉及到初始值的问题,初始值显然不能是0这里取个中间值500;关于初始值以及0-1000范围值的设置我们放到初始化函数CsimpleMusicPlayerDlg::OnInitDialog()中,添加以下代码:
m_slider.SetRange(0, 1000);//设置滑动条数值范围
m_slider.SetPos(500);//设置滑动条初始位置
另外,在播放函数CsimpleMusicPlayerDlg::OnBnClickedPlay()中添加:
//设置初始音量值
mciSendString(L"setaudio bgm volume to 500", 0, 0, 0);
我们再回到对话框界面,右键滑动条,选择添加事件处理程序,注意类不要选错,这样在simpleMusicPlayerDlg.cpp又多了一个函数void CsimpleMusicPlayerDlg::OnNMCustomdrawSlider(NMHDR* pNMHDR, LRESULT* pResult) { };在这个函数中我们添加如下代码:
//调整音量值
int sliderPos=m_slider.GetPos(); //获取滑动条位置即音量值
CString temp;
temp.Format(L"setaudio bgm volume to %d",sliderPos); //将音量值转成字符串
mciSendString(temp, 0, 0, 0);
11、以上,简易音乐播放器的功能部分基本具备了,下面对对话框界面做一些美化。
a. 把笔者的头像放到对话框的左下角。
步骤:把头像文件LaoWaiHang.bmp存放到工作目录中的res中 ->进入资源视图->右键simpleMusicPlayer.rc->添加资源->点击Bitmap->导入->选择存入的图片。
此时,在查看资源视图中的bitmap目录,可看到图片的资源ID为IDB_BITMAP1。
接着在对话框上添加图片控件,右键控件在下部类型中选择“Bitmap”,在图像中选择Bitmap图片的资源ID号。在“居中图像”中选“true”。(注意:将图片的大小调整到与图片控件一致,否则显示不全)
b.在头像的右边拖入一个静态文本框,写上一些简介文字。
c.在对话框的右上角添加一个播放显示的简单动画。以前设计过一段利用随机数模拟音乐播放的代码,现将这些代码放到进度条显示的函数CsimpleMusicPlayerDlg::OnTimer(UINT_PTR nIDEvent)中。运行条件也和进度条运行条件一致,都是isShow为TRUE。代码具体如下所示:
CRect rect;//定义一个矩形区域
GetClientRect(rect); //获取客户区数据存入rect
int dlgWidth = rect.Width(); //获取窗体宽度
int dlgHeight = rect.Height(); //获取窗体高度
//显示区域
int x1 = dlgWidth * 0.8; int y1 = dlgHeight * 0.12;
int x2 = dlgWidth * 0.85;int y2 = dlgHeight * 0.17;
//波动范围
int scale = y2 - y1;
int ys1=rand() % scale;
int ys2=rand() % scale;
int ys3=rand() % scale;
RedrawWindow(CRect(x1-5,y1-5,x2+5,y2+5));//重绘指定区域
if (isShow) {
CDC* pDC = GetDC(); // 创建画笔工具
CPen myPen,*pOldPen;
myPen.CreatePen(0,3,0x0000FF);
pOldPen=pDC->SelectObject(&myPen);
pDC->MoveTo(x1,y2);
pDC->LineTo(x1,y2-ys1);
pDC->MoveTo(x1+(x2-x1)/2,y2);
pDC->LineTo(x1+(x2-x1)/2,y2-ys2);
pDC->MoveTo(x2,y2);
pDC->LineTo(x2,y2-ys3);
pDC->SelectObject(pOldPen);//恢复缺省画笔
ReleaseDC(pDC);
}
12、至此,关于简易音乐播放器的制作过程及相关代码介绍完了。实际运行的界面截图如下: