当前位置:C++技术网 > 资讯 > MFC学习实践之简易音乐播放器(四)添加进度、音量跳转及列表文件情况备份还原

MFC学习实践之简易音乐播放器(四)添加进度、音量跳转及列表文件情况备份还原

更新时间:2017-03-18 21:11:12浏览次数:1+次

本节目录:

1.       前几节内容回顾与总结

2.       添加进度及音量跳转功能

3.       添加音乐列表清空、保存及还原功能

4.       完整功能代码实现

5.       演示


1. 前几节内容回顾与总结

    从第一节到现在,功能在不断完善,代码在不断地增加,同时难免会出现不同的BUG.

第一节的API函数是支撑起整个播放器的核心:MCIERROR mciSendString(LPCTSTR lpszCommand, LPTSTR lpszReturnString, UINT cchReturn, HANDLE hwndCallback )。在后面的内容中,有疑问或者记不清的可翻阅到第一节去查看。

第一节链接:MFC学习实践之简易音乐播放器(一)主界面演示及基本原理说明

    第二节开始,重写音乐播放器,从一个最简单的,只有播放停止功能开始,逐步完善添加!

第二节链接:MFC学习实践之简易音乐播放器(二)完整主界面控件说明及编写最简音乐播放器

    第三节,功能比上一节稍有增加,多个音乐文件列表,上一曲及下一曲等功能,代码也进行了一定的重构与封装,方便后面功能扩展!很多时候,功能越多函数封装就越重要,对于经常用到的子功能,写入不同的函数中,可以省去很多时间及重复性的代码!

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

    现在,第四节开始了,在本节中一个简单的音乐播放器大致成型!音乐文件列表,自动备份及还原,上一曲,下一曲,进度、音量设置调整,列表添加与情况!(全盘音乐文件扫描功能待添加 及播放模式设置待添加)


2. 添加进度及音量跳转功能

         利用VS2017的即拖即放功能,对上一节的界面重新布局,增加2个滑块控件,第一个控制进度条,第二个控制音量!本节完整界面控件设计图如下:


进度条控件ID: IDC_SLIDERSTEP

音量控件ID: IDC_SLIDERVOLUME

 

添加对应的消息相应函数:


滑块控件常用成员函数:

  GetRange,SetRange

用于查询和设置滑动条的取值范围,默认为0~100。函数定义形式如下:

void GetRange(int &nMin,int &nMax) const; //查询滑块取值范围

void SetRange(int nMin,int nMax,BOOL bRedrGETaw=FALSE);//设置滑块取值范围

  GetPos,SetPos

用于查询和设置滑动条的当前值。函数定义形式如下:

int GetPos() const;//获取滑动条当前值

int SetPos(int nPos);//设置滑块当前值

SetTicFreq函数

用于设置滑动条刻度的频度。默认为一个单位一个函数。函数定义形式如下:

void SetTicFreq(int nFreq);


进度跳转响应函数:

 

void CMusicPlayerDlg::OnNMCustomdrawSliderstep(NMHDR *pNMHDR, LRESULT *pResult) //进度条滑块响应函数
{
	LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);
	// TODO: 在此添加控件通知处理程序代码
	if (m_PlayStatus == 2)	//判断是否
	{
		int pos = m_ctrStep.GetPos();
		if (abs(pos - m_nowPlayStep) > 2)	//误差判定  若变化值大于2  视为人工改变进度条
		{
			m_nowPlayStep = pos;
			TCHAR cmd[MAX_PATH] = L"play";
			TCHAR cmdParam[MAX_PATH] = L"";
			wsprintf(cmdParam, L"%d", m_nowPlayStep * 1000);
			StatusMessageDistribution(cmd, cmdParam, MAX_PATH);//自定义音乐控制消息分配函数

		}
	}
	*pResult = 0;
}


音量设置响应函数:

 

void CMusicPlayerDlg::OnNMCustomdrawSlidervolume(NMHDR *pNMHDR, LRESULT *pResult)	//声音滑块响应函数
{
	LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);
	// TODO: 在此添加控件通知处理程序代码
	
	int pos = m_ctrVolume.GetPos();
	if (m_PlayStatus == 2)	//判断是否出于播放状态
	{
		if (abs(pos - m_nowPlayVolume) > 2) //误差判定  若变化值大于2  视为人工改变进度条
		{
			m_nowPlayVolume = pos;
			SetVolume(); //自定义的音量设置函数

		}
	}
	else
	{
		m_nowPlayVolume = pos;
	}
	m_strVolume.Format(L"%d/100", m_nowPlayVolume);
	UpdateData(FALSE);
	*pResult = 0;
}


 

3.添加音乐列表清空、保存及还原功能

音乐文件列表清空

在文件主菜单下添加子菜单功能:

清空列表菜单ID设置为:ID_MENUCLEAR


如上图,添加事件处理处理程序代码:

 

void CMusicPlayerDlg::OnMenuclear()	//情况列表
{
	// TODO: 在此添加命令处理程序代码
	if (m_PlayStatus>0)
		StatusMessageDistribution(_T("close"));
	m_ctrMusicList.DeleteAllItems();
	int count = m_MusicPathList.GetCount();
	for (int i = 0; i < count; i++)
	{
		 m_MusicPathList.RemoveTail();
	}

}


  音乐文件列表备份

         遍历列表控件中保存的数据,再程序关闭之时自动存储至本地磁盘文件中!

代码为:

nt CMusicPlayerDlg::SaveListToFile()	//音乐文件列表保存
{
	char* old_locale = _strdup(setlocale(LC_CTYPE, NULL));
	setlocale(LC_CTYPE, "chs");//设定为中文环境,解决UNICODE环境下中文字符储存问题

	CStdioFile file;
	BOOL r=file.Open(_T("musiclist.dat"), CStdioFile::modeCreate | CStdioFile::modeWrite);
	if (r == FALSE)
	{
		MessageBox(L"文件写入失败!");
		setlocale(LC_CTYPE, old_locale);
		free(old_locale);//还原区域设定
		return -1;
	}
	
	TCHAR wtBuffer[1024]=L"";
	int count = m_ctrMusicList.GetItemCount();
	for (int i = 0; i < count; i++)
	{
		//CString name=m_ctrMusicList.GetItemText(i, 0);
		CString *path =(CString*)m_ctrMusicList.GetItemData(i);
		ASSERT(path != NULL);
		wsprintf(wtBuffer, L"%s\n", path->GetString());
		//MessageBox(wtBuffer);
		file.WriteString(wtBuffer);
	}
	file.Close();
	//文件操作结束后,还原
	setlocale(LC_CTYPE, old_locale);
	free(old_locale);//还原区域设定
	return 0;
}


音乐文件列表还原

读取备份的文件,添加至文件列表中。

代码为:

int CMusicPlayerDlg::ReadFileToList()	//音乐文件列表还原
{
	//设定为中文环境,解决UNICODE环境下中文字符储存问题
	char* old_locale = _strdup(setlocale(LC_CTYPE, NULL));
	setlocale(LC_CTYPE, "chs");
	CStdioFile file;
	BOOL r = file.Open(_T("musiclist.dat"), CStdioFile::modeRead);
	if (r == FALSE)
	{
		setlocale(LC_CTYPE, old_locale);
		free(old_locale);//还原区域设定
		MessageBox(L"文件读取失败!");
		return -1;
	}
	TCHAR rdBuffer[1024] = L"";
	TCHAR name[MAX_PATH] = L"";
	TCHAR *path = NULL;
	while (file.ReadString(rdBuffer,1024))
	{
		int size = lstrlen(rdBuffer);
		rdBuffer[size - 1] = '\0';
		AddMusicToList(rdBuffer);
	}
	file.Close();

	//文件操作结束后,还原
	setlocale(LC_CTYPE, old_locale);
	free(old_locale);//还原区域设定
	return 0;
}


对于文件列表备份与还原功能,这里有一个缺陷,无法保护储存的数据。一个简单的解决方法是:可在储存时采用异或进行加密,读取时异或进行解密,达到保护数据的目的!这里可自行添加!

 

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 m_nowPlayIndex=-1;
	long m_nowPlayStep = 0;
	int m_nowPlayLength = 0;
	int m_nowPlayVolume = 50;
	CList<CString, CString&> m_MusicPathList;

	int SaveListToFile();
	int ReadFileToList();
	int SetVolume();
	int GetInfo();
	void AddMusicToList(CString FilePath);
	int StatusMessageDistribution(CString cmd, TCHAR* cmdParam = NULL, int ParamLen = 0);
	
public:
	CButton m_btnPlay;
	CString m_strVolume;
	CString m_strStep;
	CSliderCtrl m_ctrStep;
	CSliderCtrl m_ctrVolume;
	CListCtrl m_ctrMusicList;

	afx_msg void OnDestroy();
	afx_msg void OnClickedIdplay();
	afx_msg void OnClickedIdstop();
	afx_msg void OnMenuaddfile();
	afx_msg void OnAppAbout();
	afx_msg void OnMenuexit();
	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();
	afx_msg void OnMenuclear();
	afx_msg void OnTimer(UINT_PTR nIDEvent);
	afx_msg void OnNMCustomdrawSliderstep(NMHDR *pNMHDR, LRESULT *pResult);
	afx_msg void OnNMCustomdrawSlidervolume(NMHDR *pNMHDR, LRESULT *pResult);
	
}; 
(2)MusicPlayerDlg.cpp : 实现文件


// MusicPlayerDlg.cpp : 实现文件
//

#include "stdafx.h"
#include "MusicPlayer.h"
#include "MusicPlayerDlg.h"
#include "afxdialogex.h"
#include <mmsystem.h>
#include <locale.h>
#pragma comment(lib,"winmm.lib")
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define TIMERPLAYSTEP 1
// 用于应用程序“关于”菜单项的 CAboutDlg 对话框

class CAboutDlg : public CDialogEx
{
public:
	CAboutDlg();

// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_ABOUTBOX };
#endif

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

// 实现
protected:
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CMusicPlayerDlg 对话框



CMusicPlayerDlg::CMusicPlayerDlg(CWnd* pParent /*=NULL*/)
	: CDialogEx(IDD_MUSICPLAYER_DIALOG, pParent)
	, m_strPath(_T(""))
	, m_strNowPlayPath(_T(""))
	, m_strVolume(_T("50/100"))
	, m_strStep(_T("0:0/0:0"))
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CMusicPlayerDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);

	DDX_Text(pDX, IDC_STATICNOWPLAYPATH, m_strNowPlayPath);
	DDX_Control(pDX, IDPLAY, m_btnPlay);
	DDX_Control(pDX, IDC_LISTMUSIC, m_ctrMusicList);
	DDX_Text(pDX, IDC_STATICVOLUME, m_strVolume);
	DDX_Text(pDX, IDC_STATICSTEP, m_strStep);
	DDX_Control(pDX, IDC_SLIDERSTEP, m_ctrStep);
	DDX_Control(pDX, IDC_SLIDERVOLUME, m_ctrVolume);
}

BEGIN_MESSAGE_MAP(CMusicPlayerDlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
//	ON_BN_CLICKED(IDCANCEL, &CMusicPlayerDlg::OnBnClickedCancel)
	ON_WM_DESTROY()
	ON_BN_CLICKED(IDPLAY, &CMusicPlayerDlg::OnClickedIdplay)
	ON_BN_CLICKED(IDSTOP, &CMusicPlayerDlg::OnClickedIdstop)
	ON_COMMAND(ID_MENUADDFILE, &CMusicPlayerDlg::OnMenuaddfile)
	ON_COMMAND(ID_APP_ABOUT, &CMusicPlayerDlg::OnAppAbout)
	ON_COMMAND(ID_MENUEXIT, &CMusicPlayerDlg::OnMenuexit)
	ON_NOTIFY(LVN_ITEMCHANGED, IDC_LISTMUSIC, &CMusicPlayerDlg::OnLvnItemchangedListmusic)
	ON_NOTIFY(NM_DBLCLK, IDC_LISTMUSIC, &CMusicPlayerDlg::OnDblclkListmusic)
	ON_NOTIFY(NM_RCLICK, IDC_LISTMUSIC, &CMusicPlayerDlg::OnRclickListmusic)
	ON_BN_CLICKED(IDC_BUTTONNEXT, &CMusicPlayerDlg::OnBnClickedButtonnext)
	ON_BN_CLICKED(IDC_BUTTONLAST, &CMusicPlayerDlg::OnBnClickedButtonlast)
	ON_COMMAND(ID_MENUCLEAR, &CMusicPlayerDlg::OnMenuclear)
	ON_WM_TIMER()
	ON_NOTIFY(NM_CUSTOMDRAW, IDC_SLIDERSTEP, &CMusicPlayerDlg::OnNMCustomdrawSliderstep)
	ON_NOTIFY(NM_CUSTOMDRAW, IDC_SLIDERVOLUME, &CMusicPlayerDlg::OnNMCustomdrawSlidervolume)
END_MESSAGE_MAP()


// CMusicPlayerDlg 消息处理程序

BOOL CMusicPlayerDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 将“关于...”菜单项添加到系统菜单中。

	// IDM_ABOUTBOX 必须在系统命令范围内。
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
	//  执行此操作
	SetIcon(m_hIcon, TRUE);			// 设置大图标
	SetIcon(m_hIcon, FALSE);		// 设置小图标

	// TODO: 在此添加额外的初始化代码
	m_PlayStatus = 0;
	this->SetBackgroundColor(RGB(175,238,238));
	ReadFileToList();
	m_ctrVolume.SetRange(0, 100);
	m_ctrVolume.SetPos(m_nowPlayVolume);
	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

void CMusicPlayerDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialogEx::OnSysCommand(nID, lParam);
	}
}

// 如果向对话框添加最小化按钮,则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序,
//  这将由框架自动完成。

void CMusicPlayerDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // 用于绘制的设备上下文

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// 使图标在工作区矩形中居中
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// 绘制图标
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CMusicPlayerDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}



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);//截取文件名称
		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);//绑定音乐全路径列表控件中
	}
	//	MessageBox(name);	
}
int CMusicPlayerDlg::StatusMessageDistribution(CString cmd, TCHAR* cmdParam,int ParamLen)//封装函数  音乐播放命令消息分配函数
{
	//int result = -1;
	if (cmd.IsEmpty())
		return -1;
	
	if (cmdParam==NULL)
	{
		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 music volume 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()//程序退出  内存清理
{
	

	SaveListToFile();
	OnMenuclear();
	CDialogEx::OnDestroy();
	// 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;
			m_nowPlayStep = 0;
			GetInfo();
			SetVolume();
		}
	case 1://文件打开状态播放音乐
		if (StatusMessageDistribution(_T("play")) == 0)
		{
			m_PlayStatus = 2;
			m_strNowPlayPath = m_ctrMusicList.GetItemText(m_nowPlayIndex, 0);
			m_btnPlay.SetWindowTextW(_T("暂停"));//设置按钮文本显示为暂停
			UpdateData(FALSE);
			SetTimer(TIMERPLAYSTEP, 1000,NULL);
		}
		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)
	{
		KillTimer(TIMERPLAYSTEP);
		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("当前已处于第一首音乐!"));
	}
}
void CMusicPlayerDlg::OnBnClickedOk()
{
	// TODO: 在此添加控件通知处理程序代码


	//	CDialogEx::OnOK();
}


void CMusicPlayerDlg::OnBnClickedCancel()
{
	// TODO: 在此添加控件通知处理程序代码

	//CDialogEx::OnCancel();
}

int CMusicPlayerDlg::SaveListToFile()	//音乐文件列表保存
{
	char* old_locale = _strdup(setlocale(LC_CTYPE, NULL));
	setlocale(LC_CTYPE, "chs");//设定为中文环境,解决UNICODE环境下中文字符储存问题

	CStdioFile file;
	BOOL r=file.Open(_T("musiclist.dat"), CStdioFile::modeCreate | CStdioFile::modeWrite);
	if (r == FALSE)
	{
		MessageBox(L"文件写入失败!");
		setlocale(LC_CTYPE, old_locale);
		free(old_locale);//还原区域设定
		return -1;
	}
	
	TCHAR wtBuffer[1024]=L"";
	int count = m_ctrMusicList.GetItemCount();
	for (int i = 0; i < count; i++)
	{
		//CString name=m_ctrMusicList.GetItemText(i, 0);
		CString *path =(CString*)m_ctrMusicList.GetItemData(i);
		ASSERT(path != NULL);
		wsprintf(wtBuffer, L"%s\n", path->GetString());
		//MessageBox(wtBuffer);
		file.WriteString(wtBuffer);
	}
	file.Close();
	//文件操作结束后,还原
	setlocale(LC_CTYPE, old_locale);
	free(old_locale);//还原区域设定
	return 0;
}


int CMusicPlayerDlg::ReadFileToList()	//音乐文件列表还原
{
	//设定为中文环境,解决UNICODE环境下中文字符储存问题
	char* old_locale = _strdup(setlocale(LC_CTYPE, NULL));
	setlocale(LC_CTYPE, "chs");
	CStdioFile file;
	BOOL r = file.Open(_T("musiclist.dat"), CStdioFile::modeRead);
	if (r == FALSE)
	{
		setlocale(LC_CTYPE, old_locale);
		free(old_locale);//还原区域设定
		MessageBox(L"文件读取失败!");
		return -1;
	}
	TCHAR rdBuffer[1024] = L"";
	TCHAR name[MAX_PATH] = L"";
	TCHAR *path = NULL;
	while (file.ReadString(rdBuffer,1024))
	{
		int size = lstrlen(rdBuffer);
		rdBuffer[size - 1] = '\0';
		AddMusicToList(rdBuffer);
	}
	file.Close();

	//文件操作结束后,还原
	setlocale(LC_CTYPE, old_locale);
	free(old_locale);//还原区域设定
	return 0;
}


void CMusicPlayerDlg::OnMenuclear()	//情况列表
{
	// TODO: 在此添加命令处理程序代码
	if (m_PlayStatus>0)
		StatusMessageDistribution(_T("close"));
	m_ctrMusicList.DeleteAllItems();
	int count = m_MusicPathList.GetCount();
	for (int i = 0; i < count; i++)
	{
		 m_MusicPathList.RemoveTail();
	}

}


int CMusicPlayerDlg::GetInfo()	//获取音乐文件信息
{
	TCHAR cmd[MAX_PATH] = L"status";
	TCHAR szlen[MAX_PATH] = L"";
	StatusMessageDistribution(cmd, szlen, MAX_PATH);
	m_nowPlayLength =_wtoi(szlen);
	m_nowPlayLength /= 1000;
	m_ctrStep.SetRange(0, m_nowPlayLength);
	m_ctrStep.SetPos(0);
	m_strStep.Format(L"%2d:%2d/%2d:%2d",m_nowPlayStep/60,m_nowPlayStep%60, m_nowPlayLength /60, m_nowPlayLength %60);
	UpdateData(FALSE);
	return 0;
}


void CMusicPlayerDlg::OnTimer(UINT_PTR nIDEvent)	//定时器响应函数
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	switch (nIDEvent)
	{
	case TIMERPLAYSTEP:	//进度条变化
		if (m_PlayStatus == 2)
		{
			if (m_nowPlayStep < m_nowPlayLength)
			{
				m_nowPlayStep += 1;
				m_strStep.Format(L"%2d:%2d/%2d:%2d", m_nowPlayStep / 60, m_nowPlayStep % 60, m_nowPlayLength / 60, m_nowPlayLength  % 60);
				m_ctrStep.SetPos(m_nowPlayStep);
				UpdateData(FALSE);
			}
			else //若播放完毕  自动跳转下一曲
			{
				OnBnClickedButtonnext();
			}
		}
		break;
	default:
		break;
	}
	CDialogEx::OnTimer(nIDEvent);
}


void CMusicPlayerDlg::OnNMCustomdrawSliderstep(NMHDR *pNMHDR, LRESULT *pResult) //进度条滑块响应函数
{
	LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);
	// TODO: 在此添加控件通知处理程序代码
	if (m_PlayStatus == 2)	//判断是否
	{
		int pos = m_ctrStep.GetPos();
		if (abs(pos - m_nowPlayStep) > 2)	//误差判定  若变化值大于2  视为人工改变进度条
		{
			m_nowPlayStep = pos;
			TCHAR cmd[MAX_PATH] = L"play";
			TCHAR cmdParam[MAX_PATH] = L"";
			wsprintf(cmdParam, L"%d", m_nowPlayStep * 1000);
			StatusMessageDistribution(cmd, cmdParam, MAX_PATH);//自定义音乐控制消息分配函数

		}
	}
	*pResult = 0;
}


void CMusicPlayerDlg::OnNMCustomdrawSlidervolume(NMHDR *pNMHDR, LRESULT *pResult)	//声音滑块响应函数
{
	LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);
	// TODO: 在此添加控件通知处理程序代码
	
	int pos = m_ctrVolume.GetPos();
	if (m_PlayStatus == 2)	//判断是否出于播放状态
	{
		if (abs(pos - m_nowPlayVolume) > 2) //误差判定  若变化值大于2  视为人工改变进度条
		{
			m_nowPlayVolume = pos;
			SetVolume(); //自定义的音量设置函数

		}
	}
	else
	{
		m_nowPlayVolume = pos;
	}
	m_strVolume.Format(L"%d/100", m_nowPlayVolume);
	UpdateData(FALSE);
	*pResult = 0;
}


int CMusicPlayerDlg::SetVolume()	//设置声音
{
	TCHAR cmd[MAX_PATH] = L"setaudio volume";
	TCHAR cmdParam[MAX_PATH] = L"";
	wsprintf(cmdParam, L"%d", m_nowPlayVolume * 10);
	StatusMessageDistribution(cmd, cmdParam, MAX_PATH);
	return 0;
} 


5.演示

 

本节结束语:

    从开始着手到现在,中间磕磕碰碰,所谓学如逆水行舟,不进则退!虽然以前用VC6.0写过一遍,但现在利用VS2017重写一遍,完全是一种全新的体验,很多功能,很多函数,不去温故,很容易就忘记了!

    从第一节开始到现如今,功能基本完善了!或者下一节就是终点,又或者将一直更新下去,弥补之前写的旧版本所遗留下的一些遗憾!

    下一节,会把计划会添加全盘扫描音乐文件功能,播放模式设置功能!或者再加一些小的功能,暂时未想到,待定!

(没进行太多测试,中间难免有BUG或遗漏错误之处!)