MFC编程总结,音乐播放器制作

  自从退出ACM集训队之后,就很少写关于技术的文章了。。以前在QQ空间写的很多关于ACM的解题报告也觉得写的太幼稚而删掉了。现在写的这篇总结应该在比较长的一段时间里,都应该会用到吧。其实QQ空间的日志是很好的记录技术细节的东西了,因为他和QQ连在一起,所以很方便的就可以进入查看。常用而且不好记的东西,写在QQ空间里面真的是不错的选择~
    言归正传,音乐播放器其实我在大一的时候就用非常生硬的语法写出过一个非常简陋的播放器。这次写的这个是以前那个的加强版。虽然说还是有很多bug,而且我至今不知道那些BUG是什么地方的问题。。
    先给个截图吧,播放器的界面:

图片

其实看着还是有那么回事的。。恩,其实除了波形和在线下载的话,功能和酷狗,以及千千静听等播放器的功能是一样的。

音乐播放器的关键是播放音乐,所以第一步是要设计一个类,这个类拥有各种各样的播放音乐的能力。

我设计的类名字叫Player

 class Player 
{
public:
 Player();
 virtual ~Player();
 void Play();//播放函数
 void Pause();//暂停函数
 void Stop();//停止函数
 void Jump(DWORD pos);//这个函数是用来通过直接拖动进度条,然后播放从歌曲的相应位置播放的函数
 void Read(CString str);//这个函数必须在所有函数的前面调用,他有一个传入参数,是指MP3文件的路径
 void SetVolume(DWORD vol);//音量大小的设定。这个不难
 MCI_STATUS_PARMS GetMode();//这个比较关键,是获得当前的播放状态(一首歌是否播放完了就是靠他)
 MCI_STATUS_PARMS GetNow();//当前是播放的歌曲第几分第几秒?可以用来改变时间栏和进度条
 MCI_STATUS_PARMS GetLenth();//获得歌曲的长度。(时间)
private:
 MCI_PLAY_PARMS m_play;
 MCI_OPEN_PARMS m_open;
 MCIDEVICEID m_dID;
 HWND m_parent;
 MCI_STATUS_PARMS m_status;
 MCI_SEEK_PARMS m_seek;
 MCI_STATUS_PARMS m_mode;
};

各个函数(可以作为以后MCI函数的查阅手册)

实现方法是:

void Player::Read(CString str)
{
 m_dID=NULL;
 m_open.lpstrElementName=str;
 mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_ELEMENT,(DWORD)(LPVOID)&m_open);//MCI打开文件
 m_dID=m_open.wDeviceID;
}

void Player::Play()
{
 m_play.dwCallback=(DWORD)m_parent;
 mciSendCommand(m_dID,MCI_PLAY,MCI_NOTIFY,(DWORD)(LPVOID)&m_play);//播放.....下面就不写注释了
}

void Player::Pause()
{
 mciSendCommand(m_dID,MCI_PAUSE,NULL,NULL);
}

void Player::Stop()
{
 mciSendCommand(m_dID,MCI_CLOSE,NULL,NULL);
 m_status.dwReturn=0;
}

MCI_STATUS_PARMS Player::GetLenth()
{
 m_status.dwItem=MCI_STATUS_LENGTH;
 mciSendCommand(m_dID,MCI_STATUS,MCI_STATUS_ITEM,(DWORD)(LPVOID)&m_status);
 return m_status;
}

MCI_STATUS_PARMS Player::GetNow()
{
 m_status.dwItem=MCI_STATUS_POSITION;
 mciSendCommand(m_dID,MCI_STATUS,MCI_STATUS_ITEM,(DWORD)(LPVOID)&m_status);
 return m_status;
}

MCI_STATUS_PARMS Player::GetMode()
{
 m_mode.dwItem=MCI_STATUS_MODE;
 mciSendCommand(m_dID,MCI_STATUS,MCI_STATUS_ITEM,(DWORD)(LPVOID)&m_mode);
 return m_mode;
}

void Player::Jump(DWORD pos)
{
 m_seek.dwTo=pos;
 mciSendCommand(m_dID,MCI_SEEK,MCI_TO|MCI_WAIT,(DWORD)(LPVOID)&m_seek);
 mciSendCommand(m_dID,MCI_PLAY,MCI_NOTIFY,(DWORD)(LPVOID)&m_play);
}

void Player::SetVolume(DWORD vol)
{
 MCI_DGV_SETAUDIO_PARMS mcivol;
 mcivol.dwCallback=(DWORD)m_parent;
 mcivol.dwItem=MCI_DGV_SETAUDIO_VOLUME;
 mcivol.dwValue=vol;
 mciSendCommand(m_dID,MCI_SETAUDIO,MCI_DGV_SETAUDIO_VALUE|MCI_DGV_SETAUDIO_ITEM,(DWORD)(LPVOID)&mcivol);
}

基本上都是运用mciSendCommand这个函数,传入不同的参数,就可以达到不同的效果。

Player类设计完成后,在Dlg这个类当中去产生一个对象:MP3

这样就可以通过调用MP3的各个成员来播放声音了。

-------------------------------------------以上的大部分技术在大一的时候就有所了解----------------------------------------

后面的文件操作,都是以前不知道的了

首先我是在对话框上画了个列表控件。当然了,所有的歌曲数据都是写在它上面的,最最重要的就是列表的第四栏,文件地址

只要有文件地址,前三项都可以计算出来,但是为了优化电脑运算速度,可以事先把这些东西写在文件里面,这样显示的时候就不用算了

注意看:对话框初始化工作

BOOL CMusicDlg::OnInitDialog()
{
 CDialog::OnInitDialog();

 // Add "About..." menu item to system menu.

 // IDM_ABOUTBOX must be in the system command range.
 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
 ASSERT(IDM_ABOUTBOX < 0xF000);

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

 // Set the icon for this dialog.  The framework does this automatically
 //  when the application's main window is not a dialog
 SetIcon(m_hIcon, TRUE);   // Set big icon
 SetIcon(m_hIcon, FALSE);  // Set small icon
 
 // TODO: Add extra initialization here
 m_list.InsertColumn(0,"序列",LVCFMT_LEFT,40);//这里就要记住列表控件是如何让头顶上的字按要求划分的
 m_list.InsertColumn(1,"歌曲名",LVCFMT_LEFT,200);
 m_list.InsertColumn(2,"时长",LVCFMT_LEFT,50);
 m_list.InsertColumn(3,"歌曲路径",LVCFMT_LEFT,172);
 m_songsum=0;

 m_vol.SetRangeMax(1000);//滑块控件最大值设定(这里是音量最大设置)
 m_vol.SetPos(500);//初始化把音量设置为500,注意这里并没有控制音量,而只是把滑块放到中间而已

//----------------------------------------------

 m_nid.hWnd=this->m_hWnd;//这下面的都是我以前不知道的,功能是让程序在后台运行。
 m_nid.uID=IDR_MAINFRAME;//其实我也看不大懂每一句的意思,只能了解个大概,把他们当做工具吧
 m_nid.cbSize=(DWORD)sizeof(NOTIFYICONDATA);
 m_nid.uFlags=NIF_ICON|NIF_MESSAGE|NIF_TIP;
 m_nid.hIcon=LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDR_MAINFRAME));
 m_nid.uCallbackMessage=WM_MUSICHIDE;
 
 m_mode=3;//默认的播放模式是全部歌曲循环播放

 CFile file("musiclist.txt",CFile::modeRead);//这里是打开文件列表,所以即使播放器关掉了,再次打开,还是有上次加载的歌曲,不用再次 //加载了。
 char *ch;
 CString tempstr="";
 CString ss;
 DWORD len;
 int i,r;
 len=file.GetLength();//这后面的都是文件操作了,主要是把歌曲的路径以及其他信息读到列表空间上面去
 ch=new char[len+1];
 ch[len]=0;
 file.Read(ch,len);
 file.Close();
 for(i=0;i<(int)len;i++)
 {
  if(ch[i]=='!')
  {
   ss.Format("%03d",m_songsum);
   r=m_list.InsertItem(m_songsum,ss);
   m_songsum++;
   m_list.SetItemText(r,1,tempstr);
   tempstr="";
   continue;
  }
  if(ch[i]=='@')
  {
   m_list.SetItemText(r,2,tempstr);
   tempstr="";
   continue;
  }
  if(ch[i]=='\n')
  {
   m_list.SetItemText(r,3,tempstr);
   tempstr="";
   continue;
  }
  tempstr+=ch[i];
 }
 return TRUE;  // return TRUE  unless you set the focus to a control
}

发一张隐藏后的图:

图片

其实效果还是和那些播放器很像的。。~

接下来收录一个文件夹导入的函数(太多要记忆的。。)

void CMusicDlg::OnBtnOpenbrose()
{
 // TODO: Add your control notification handler code here
 BROWSEINFO bi;
 ::ZeroMemory(&bi,sizeof(bi));
 MCI_STATUS_PARMS status;

 LPITEMIDLIST target=SHBrowseForFolder(&bi);
 if(target!=NULL)
 {
  char targetpath[MAX_PATH];
  SHGetPathFromIDList(target,targetpath);

  CString tm_filepath;
  CString tm_title;
  CString tm_time;
  CString tm_p;
  CString ss;

  tm_filepath.Format("%s\\*.*",targetpath);
  CFileFind folder;
  folder.FindFile(tm_filepath);
  
  int flag=folder.FindNextFile();
  while(flag)
  {
   flag=folder.FindNextFile();
   tm_title=folder.GetFileName();
   tm_filepath=folder.GetFilePath();
   tm_p=tm_title.Right(3);

   if(tm_p=="mp3")
   {
    ss.Format("%03d",m_songsum);
    int r=m_list.InsertItem(m_songsum,ss);
    m_songsum++;
    m_list.SetItemText(r,0,ss);
    m_list.SetItemText(r,1,tm_title);
    m_list.SetItemText(r,3,tm_filepath);

    mp3.Read(tm_filepath);
    status=mp3.GetLenth();
    int min,sec;
    CString str;

    min=(status.dwReturn/1000)/60;
    sec=(status.dwReturn/1000)%60;
    CString sstr;
    sstr.Format("%02d:%02d",min,sec);
    m_list.SetItemText(r,2,sstr);

    mp3.Stop();
   }
  }
 }
}

然后其他的感觉都不是很难了。定时器这些东西虽然以前不知道,不过后来看了一下也很快就明白了

哦对了,说一下怎么实现删除单一歌曲的吧

先截个图:

图片

实现过程在孙鑫的那本书上面有。就是自己创建一个弹出菜单,不过要自己去写消息映射~

当然,删除歌曲之后前面的序号要变一下,这个很容易理解。

再来一个可能比较容易忘的:

双击列表中的歌曲就播放该歌曲

void CMusicDlg::OnDblclkList1(NMHDR* pNMHDR, LRESULT* pResult)
{
 // TODO: Add your control notification handler code here
 NMLISTVIEW* pNMLISTVIEW=(NMLISTVIEW*)pNMHDR;
 int nItem;
 nItem=pNMLISTVIEW->iItem;
 if(nItem>=0&&nItem<m_list.GetItemCount())
 {
  int now_play=nItem;
  m_filepath=m_list.GetItemText(now_play,3);
  m_nowid=now_play;
  mp3.Stop();
  mp3.Read(m_filepath);
  OnBtnPlay();
 }
 *pResult = 0;
}

主要是nItem前面那儿风骚的强制转换了一把,(我也是到处抄的。。)感觉操作略微有点飘逸。后面还是很中规中矩的停止,播放~

不过,后面删除可不能这样写,要分开成两个函数

好啦,差不多就写这么多吧~其实写完了才发现,犀利的操作并不多嘛(就当是以后的函数手册了)

猜你喜欢

转载自blog.csdn.net/xieshimao/article/details/7296167