本节目录:
1. 界面优化
2. 添加主菜单
3. 添加音乐文件列表功能
4. 功能代码实现
5. 演示
1. 界面优化
上一节所创建的音乐播放器过于简陋,而且功能位置排布很不合理,因此,对上一节的界面做出调整优化!(特此声明:随着功能的增加,会对之前的代码进行一定的封装或重构!重新调整编写的代码我会一一做出说明)
主要涉及这几个方面:音乐文件添加按钮移动至功能主菜单,添加音乐文件列表控件,添加上一曲,下一曲功能!
新的主界面控件位置如下:
列表控ID: ID_LISTMUSIC
上一曲按钮控件ID:IDC_BUTTONLAST
播放按钮控件ID: IDPLAY
停止按钮控件ID: IDSTOP
下一曲控件ID: IDC_BUTTONNEXT
2. 添加主菜单功能
菜单属于资源文件的一种,可定义多级菜单项!
在VS2017中添加主菜单的其中一种方法如下:
(1) 找到资源视图->鼠标右键->添加资源->选择Menu->新建
(2)菜单编辑
菜单新建成功后,点击进入菜单编辑界面,并添加如下菜单项。
文件:(Popup:True)
添加音乐文件(Popup:False)
ID设置为ID_MENUADDFILE
关于:(Popup:False)
ID设置为ID_APP_ABOUT
退出(Popup:False)
ID设置为ID_MENUEXIT
在添加菜单项时,有两个关键点:
Popup:是否包含子菜单
True:包含子菜单,ID属性将变为不可用状态
False:不包含子菜单,此时菜单项将变为功能菜单,此时可设置ID,并添加相应的功能函数。
(3)添加菜单响应函数
和之前添加按钮点击命令一样,找到所添加的功能菜单项的ID,利用类向导自动生成对应的功能函数!
3.添加音乐文件列表功能
添加Listbox Control控件,并添加其控件成员变量!
CListCtrl m_ctrMusicList;
一般,音乐播放器双击音乐列表将会播放音乐,为实现这个功能,我们可以利用向导生成列表控件鼠标左键双击响应函数!
针对ListBox Control的消息函数,我也只是用过其中的一部分,下面做些许说明。
LVN_ITEMCHANGED:当列表选择项发生改变时触发此消息
LVN_ITEMACTIVATE:当列表项激活的时候触发此消息
LVN_DELETEITEM:当列表项被删除的时候触发此消息
LVN_DELETEALLITEMS:当列表项被清空的时候触发此消息
LVN_INSERTITEM:当插入新的列表项时触发此消息
LVN_KEYDOWN:当按下键盘按键时触发此消息
NM_CLICK:当鼠标左键单击时触发
NM_DBLCLK;当鼠标左键双击时触发
NM_FONTCHANGED:当列表项字体改变时触发
NM_KILLFOCUS:当列表控件失去焦点时触发
NM_SETFOCUS:当列表控件获得焦点的时候触发
NM_RCLICK:当鼠标右键单击时触发
NM_RDBLCLK:当鼠标右键双击的时候触发
4.功能代码实现
(1) MusicPlayerDlg.h头文件
// MusicPlayerDlg.h : 头文件
//
#pragma once
#include "afxwin.h"
#include "afxcmn.h"
// CMusicPlayerDlg 对话框
class CMusicPlayerDlg : public CDialogEx
{
// 构造
public:
CMusicPlayerDlg(CWnd* pParent = NULL); // 标准构造函数
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_MUSICPLAYER_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
CString m_strPath;
CString m_strNowPlayPath;
afx_msg void OnClickedButtonsel();
afx_msg void OnBnClickedOk();
afx_msg void OnBnClickedCancel();
private:
int m_PlayStatus;// 音乐播放状态 0 初始状态 1 音乐文件打开或停止状态 2 音乐文件播放状态 3 音乐文件暂停状态
int StatusMessageDistribution(CString cmd, TCHAR* cmdParam=NULL, int ParamLen = 0);
int m_nowPlayIndex=-1;
void AddMusicToList(CString FilePath);
CList<CString,CString&> m_MusicPathList;
public:
afx_msg void OnDestroy();
CButton m_btnPlay;
afx_msg void OnClickedIdplay();
afx_msg void OnClickedIdstop();
afx_msg void OnMenuaddfile();
afx_msg void OnAppAbout();
afx_msg void OnMenuexit();
CListCtrl m_ctrMusicList;
afx_msg void OnLvnItemchangedListmusic(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnDblclkListmusic(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnRclickListmusic(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnBnClickedButtonnext();
afx_msg void OnBnClickedButtonlast();
};
(2)MusicPlayerDlg.cpp源代码核心文件
void CMusicPlayerDlg::OnClickedButtonsel()
{
// TODO: 在此添加控件通知处理程序代码
/*
CFileDialog MFC中的文件打开保存对话框,CFileDialog的构造函数原型为:
*/
//OFN_ALLOWMULTISELECT 允许多选标志
CFileDialog fdlg(TRUE,_T(".mp3"), NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_ALLOWMULTISELECT , _T("歌曲(.mp3)|*.mp3||"), NULL);
if(fdlg.DoModal()==IDOK)
{
POSITION pos = fdlg.GetStartPosition();
while (pos != NULL)//遍历多选文件列表
{
//在这里 GetNextPathName()函数有个BUG,当列表数目过多时,文件路径会获取错误,最终失败
CString FilePath = fdlg.GetNextPathName(pos);//获取文件对话框选择的文件全路径
// MessageBox(FilePath);
AddMusicToList(FilePath);
}
}
}
void CMusicPlayerDlg::AddMusicToList(CString FilePath)//封装函数 添加音乐文件至列表框以及链表数据中
{
CString *path = new CString(FilePath);
CString name;
int index = FilePath.ReverseFind('\\');
if (index > 0)
{
name = FilePath.Right(FilePath.GetLength() - index - 1);//截取文件名称
}
// MessageBox(name);
POSITION pos = m_MusicPathList.AddTail(*path);//音乐全路径添加至数据连表中
int count = m_ctrMusicList.GetItemCount();
m_ctrMusicList.InsertItem(count, name);//将音乐名称插入列表项中
BOOL r = m_ctrMusicList.SetItemData(count, (DWORD_PTR)path);//绑定音乐全路径列表控件中
}
int CMusicPlayerDlg::StatusMessageDistribution(CString cmd, TCHAR* cmdParam,int ParamLen)//封装函数 音乐播放命令消息分配函数
{
//int result = -1;
if (cmd.IsEmpty())
return -1;
if (lstrlen(cmdParam)<=0)
{
cmd += _T(" music");
}
else
{
if (cmd.Compare(_T("open")) == 0)//打开文件
{
cmd.Format(_T("open \"%s\" alias music"),cmdParam);
}
else if (cmd.Compare(_T("seek")) == 0)//文件定位
{
cmd.Format(_T("seek music to %s"),cmdParam);
}
else if (cmd.Compare(_T("setaudio volume")) == 0)//设置声音
{
cmd.Format(_T("setaudio volume music to %s"), cmdParam);
}
else if (cmd.Compare(_T("play")) == 0)//定位播放
{
cmd.Format(_T("play music from %s"), cmdParam);
}
else if (cmd.Compare(_T("status")) == 0)//获取文件长度
{
cmd.Format(_T("status music length"));
memset(cmdParam, 0, MAX_PATH);
}
else
{
MessageBox(cmd + "命令无法识别");
}
}
//MessageBox(cmd);
MCIERROR r = mciSendString(cmd, cmdParam, ParamLen, NULL);
if (r == 0)
{
return 0;
}
else
{
//出错判断提示
#if _DEBUG
TCHAR erStr[MAX_PATH] = _T("");
mciGetErrorString(r, erStr, MAX_PATH);//如果文件打开遇到错误,输出错误提示语句
MessageBox(cmd+erStr);
#endif
return -1;
}
return -1;
}
void CMusicPlayerDlg::OnDestroy()//程序退出 内存清理
{
CDialogEx::OnDestroy();
if(m_PlayStatus>0)
StatusMessageDistribution(_T("close"));
int count = m_MusicPathList.GetCount();
for (int i = 0; i < count; i++)
{
CString path = m_MusicPathList.RemoveTail();
}
// TODO: 在此处添加消息处理程序代码
}
void CMusicPlayerDlg::OnClickedIdplay()//音乐播放暂停
{
// TODO: 在此添加控件通知处理程序代码
int index=m_ctrMusicList.GetNextItem(-1, LVIS_SELECTED);//列表选项判断
if (index >= 0)
{
if (index != m_nowPlayIndex && m_PlayStatus>0)//判断是否播放新的音乐文件
{
OnClickedIdstop();//停止当前播放音乐
}
m_nowPlayIndex = index;
}
if (m_nowPlayIndex < 0) return;
CString *path=NULL;
TCHAR cmdPath[MAX_PATH] = _T("");
switch (m_PlayStatus)//播放状态判断
{
case 0://打开音乐文件
path=(CString*)m_ctrMusicList.GetItemData(m_nowPlayIndex);
wsprintf(cmdPath, _T("%s"), path->GetString());
ASSERT(path != NULL);
//MessageBox(*path);
if (StatusMessageDistribution(_T("open"),cmdPath) == 0)
{
m_PlayStatus = 1;
}
case 1://文件打开状态播放音乐
if (StatusMessageDistribution(_T("play")) == 0)
{
m_PlayStatus = 2;
m_strNowPlayPath = m_ctrMusicList.GetItemText(m_nowPlayIndex, 0);
m_btnPlay.SetWindowTextW(_T("暂停"));//设置按钮文本显示为暂停
UpdateData(FALSE);
}
break;
case 2://文件播放状态暂停音乐
if (StatusMessageDistribution(_T("pause")) == 0)
{
m_PlayStatus = 3;
//m_strNowPlayPath = _T("正在暂停歌曲!");
m_btnPlay.SetWindowTextW(_T("播放"));//设置按钮文本显示为暂停
//UpdateData(FALSE);
}
break;
case 3:
if (StatusMessageDistribution(_T("resume")) == 0)
{
m_PlayStatus = 2;
//m_strNowPlayPath = _T("正在播放歌曲!");
m_btnPlay.SetWindowTextW(_T("暂停"));//设置按钮文本显示为暂停
//UpdateData(FALSE);
}
break;
}
}
void CMusicPlayerDlg::OnClickedIdstop()//音乐停止
{
if (StatusMessageDistribution(_T("close")) == 0)
{
m_PlayStatus = 0;//播放状态初始化
m_nowPlayIndex = -1;//播放序号初始化
m_strNowPlayPath.Empty();//字符串提示情况
m_btnPlay.SetWindowTextW(_T("播放"));//设置按钮文本显示为暂停
UpdateData(FALSE);
}
// TODO: 在此添加控件通知处理程序代码
}
void CMusicPlayerDlg::OnMenuaddfile()//添加音乐文件菜单响应函数
{
// TODO: 在此添加命令处理程序代码
OnClickedButtonsel();
}
void CMusicPlayerDlg::OnAppAbout()//关于菜单响应函数
{
// TODO: 在此添加命令处理程序代码
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
void CMusicPlayerDlg::OnMenuexit()//程序退出菜单响应函数
{
// TODO: 在此添加命令处理程序代码
PostQuitMessage(0);
}
void CMusicPlayerDlg::OnLvnItemchangedListmusic(NMHDR *pNMHDR, LRESULT *pResult)//列表选中项改变时触发的响应消息函数
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
// TODO: 在此添加控件通知处理程序代码
if (m_nowPlayIndex == pNMLV->iItem)
{
m_btnPlay.SetWindowText(_T("暂停"));
}
else
{
m_btnPlay.SetWindowText(_T("播放"));
}
*pResult = 0;
}
void CMusicPlayerDlg::OnDblclkListmusic(NMHDR *pNMHDR, LRESULT *pResult)//鼠标左键双击响应函数
{
LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
// TODO: 在此添加控件通知处理程序代码
OnClickedIdplay();
*pResult = 0;
}
void CMusicPlayerDlg::OnRclickListmusic(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
// TODO: 在此添加控件通知处理程序代码
*pResult = 0;
}
void CMusicPlayerDlg::OnBnClickedButtonnext()//下一曲功能响应函数
{
// TODO: 在此添加控件通知处理程序代码
int count = m_ctrMusicList.GetItemCount();
if (m_nowPlayIndex < (count - 1))//音乐边界判断
{
//m_nowPlayIndex += 1;
m_ctrMusicList.SetItemState(m_nowPlayIndex+1, LVIS_SELECTED, LVIS_SELECTED);
OnClickedIdplay();
}
else
{
MessageBox(_T("当前已是最后一首音乐!"));
}
}
void CMusicPlayerDlg::OnBnClickedButtonlast()
{
// TODO: 在此添加控件通知处理程序代码
if (m_nowPlayIndex >0)//音乐边界判断
{
//m_nowPlayIndex -= 1;
m_ctrMusicList.SetItemState(m_nowPlayIndex-1, LVIS_SELECTED, LVIS_SELECTED);
OnClickedIdplay();
}
else
{
MessageBox(_T("当前已处于第一首音乐!"));
}
}
5.演示
(1)音乐文件添加
(2)音乐播放主界面
小结:至此,这一节的内容也结束了,从VC6.0到VS2017,重写这音乐播放器,相同的功能,最终的代码却有些不同了!新的界面,新的功能,新的特性,虽然中间出现了许多从没遇到的新BUG,经过不断调试,最终还是成功完成了基础功能!相同的道路,不一样的走法与经历,终归是有着不同的收获与感想!
这一次文件添加的功能调用了MFC的函数,最终却出现了新的bug,每次多选的文件超过一定数量后,会出现文件路径解析错误的BUG,也是很头疼,想了好久,有点晚了,也就暂时先放下了,后面有时间再慢慢研究!
因为只是中间版本,界面一直没有美化,有兴趣的可以自行调整添加,代码越来越多,待播放器基础功能全部实现后,我会把整个vs2017的简易播放器源代码及工程文件上传分享!
到这里,也就告一段了!
下一节我会继续完善这个播放器,添加进度条控制功能,音量大小控制及音乐列表文件保存与还原功能!有空的话我会尝试着分析今天留下的BUG!
头一次分享自己的学习实践经历,中间难免会有许多遗漏或错误,有什么意见或建议的,欢迎大家一起讨论!