当前位置:C++技术网 > 资讯 > MFC学习实践之简易音乐播放器(三)添加音乐列表文件及功能菜单选项

MFC学习实践之简易音乐播放器(三)添加音乐列表文件及功能菜单选项

更新时间:2017-03-10 00:39:54浏览次数:1+次

本节目录:

1. 界面优化

2. 添加主菜单

3. 添加音乐文件列表功能

4. 功能代码实现

5. 演示

 

1. 界面优化

    上一节所创建的音乐播放器过于简陋,而且功能位置排布很不合理,因此,对上一节的界面做出调整优化!(特此声明:随着功能的增加,会对之前的代码进行一定的封装或重构!重新调整编写的代码我会一一做出说明)

    主要涉及这几个方面:音乐文件添加按钮移动至功能主菜单,添加音乐文件列表控件,添加上一曲,下一曲功能!

    新的主界面控件位置如下:


列表控ID: ID_LISTMUSIC

上一曲按钮控件IDIDC_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!

    头一次分享自己的学习实践经历,中间难免会有许多遗漏或错误,有什么意见或建议的,欢迎大家一起讨论!