公司需在上班 (早)、用餐 (中)、下班 (晚) 三個時段各播放 1 小時背景音樂,要求:
| 功能 | 技術/類別 | 說明 |
|---|---|---|
| 音訊播放 | MCI (Media Control Interface) / CCOMMCI 封裝類 | Windows 多媒體標準 API,支援 MP3/WAV/MID 等 |
| 定時排程 | 多 SetTimer (ID 1,2,3,4) | ID1: 時段檢查(1秒) ID2: 進度更新(1秒) ID3: 時段結束 ID4: 啟動隱藏 |
| 檔案選擇 | CFileDialog 多選 + 多副檔名過濾 | OFN_ALLOWMULTISELECT |
| 目錄選擇 | SHBrowseForFolder / BROWSEINFO | 配置時段音樂目錄 |
| 托盤常駐 | Shell_NotifyIcon (NIM_ADD) + WS_EX_TOOLWINDOW | 隱藏工作列按鈕,僅顯示托盤圖示 |
| 進度條 | CScrollBar 模擬 / CSliderCtrl | 範圍 0~總秒數,拖曳 Seek |
| 自訂繪製 | COwnerDrawDialog 基類 | 橢圓按鈕、彩色標題列、漸層背景 |
void CMp3PlayerDlg::OnOpen()
{
m_MusicList.ResetContent();
m_MusicAry.RemoveAll();
m_Music.Close(); // 關閉當前播放
Init();
m_Pause.SetWindowText(_T("暫停"));
// 建構多群組過濾器字串
CString filter = _T("媒體檔案 (所有類型)|*.cda;*.mid;*.rmi;*.midi;*.asf;*.wm;*.wma;*.asx;*.wax;*.m3u;*.wvx;")
_T("*.mpeg;*.mpg;*.m1v;*.mp3;*.avi;*.wmv;*.wav;*.snd;*.au;*.aif;*.aifc|");
filter += _T("CD 音軌 (*.cda)|*.cda|")
_T("MIDI 檔案 (*.mid;*.rmi;*.midi)|*.mid;*.rmi;*.midi|")
_T("Windows Media (*.asf;*.wm;*.wma;*.wa?)|*.asf;*.wm;*.wma;|")
_T("播放清單 (*.asx;*.wax;*.m3u;*.wvx)|*.asx;*.wax;*.m3u;*.wvx|")
_T("MPEG 影音 (*.mpeg;*.mpg;*.m1v;*.mp3)|*.mpeg;*.mpg;*.m1v;*.mp3|")
_T("AVI/WMV 影片 (*.avi;*.wmv)|*.avi;*.wmv|")
_T("音訊檔案 (*.wav;*.snd;*.au;*.aif;*.aifc)|*.wav;*.snd;*.au;*.aif;*.aifc|")
_T("所有檔案 (*.*)|*.*|");
CFileDialog dlg(TRUE, NULL, NULL,
OFN_HIDEREADONLY | OFN_ALLOWMULTISELECT,
filter, this);
// 重要:多選時需指定足夠緩衝區
dlg.GetOFN().lpstrFile = new TCHAR[10240];
dlg.GetOFN().nMaxFile = 10240;
if (dlg.DoModal() == IDOK)
{
POSITION pos = dlg.GetStartPosition();
while (pos != NULL)
{
CString path = dlg.GetNextPathName(pos);
m_MusicAry.Add(path); // 完整路徑陣列
// 列表顯示僅檔名
int idx = path.ReverseFind('\\');
CString name = (idx == -1) ? path : path.Mid(idx + 1);
m_MusicList.AddString(name);
}
}
delete[] dlg.GetOFN().lpstrFile;
}
void CMp3PlayerDlg::Play(int index)
{
if (index < 0 || index >= m_MusicAry.GetSize()) return;
m_Music.Close(); // 關閉前一首
m_MusicList.SetCurSel(index);
CString path = m_MusicAry.GetAt(index);
m_Music.Open(path); // MCI open command
m_Status.SetWindowText(path + _T(" 正在播放..."));
// 取得總長度 (毫秒)
m_nLength = m_Music.GetLength();
m_nPos = 0;
m_nTime = m_nLength / 1000; // 總秒數
// 格式化顯示
m_sMusicLen.Format(_T("%d分%d秒"), m_nTime / 60, m_nTime % 60);
m_sMusicPos.Format(_T("%d分%d秒"), 0, 0);
UpdateData(FALSE);
// 設定進度條範圍
m_MPBar.SetScrollRange(0, m_nTime);
m_MPBar.SetScrollPos(0);
// 音量滑桿
m_Volume.SetRange(0, 1000);
m_Volume.SetPos(m_nVolume);
// 開始播放
m_Music.Play(m_hWnd); // MCI play notify -> 視窗接收 MM_MCINOTIFY
// 啟動進度更新定時器 (1 秒)
SetTimer(2, 1000, NULL);
}
void CMp3PlayerDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
if (pScrollBar == &m_MPBar)
{
switch (nSBCode)
{
case SB_THUMBTRACK: // 拖曳中
case SB_THUMBPOSITION: // 釋放
m_nPos = nPos;
m_MPBar.SetScrollPos(m_nPos);
m_Music.Seek(m_nPos * 1000); // MCI seek (毫秒)
// 若原本暫停,Seek 後自動繼續
if (m_bPause)
{
m_bPause = FALSE;
m_Pause.SetWindowText(_T("暫停"));
m_Music.Play(m_hWnd);
}
break;
}
}
COwnerDrawDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}
BOOL CMp3PlayerDlg::OnInitDialog()
{
// ... 基類初始化 ...
// 1. 設定 WS_EX_TOOLWINDOW 隱藏工作列按鈕
LONG exStyle = GetWindowLong(GetSafeHwnd(), GWL_EXSTYLE);
SetWindowLong(GetSafeHwnd(), GWL_EXSTYLE, exStyle | WS_EX_TOOLWINDOW);
// 2. 建立托盤圖示
NOTIFYICONDATA nid = {0};
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.hWnd = m_hWnd;
nid.uID = IDI_MAINFRAME;
nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
nid.uCallbackMessage = WM_MY_TRAY_NOTIFY; // 自訂訊息
nid.hIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_MAINFRAME));
_tcscpy_s(nid.szTip, _T("企業背景音樂播放器"));
Shell_NotifyIcon(NIM_ADD, &nid);
// 3. 啟動自動隱藏定時器 (100ms 後隱藏主視窗)
SetTimer(4, 100, NULL);
// 4. 若啟用自動播放,啟動時段檢查定時器
if (m_bAutoRun) SetTimer(1, 1000, NULL);
return TRUE;
}
LRESULT CMp3PlayerDlg::OnTrayNotify(WPARAM wParam, LPARAM lParam)
{
if (wParam != IDI_MAINFRAME) return 0;
if (lParam == WM_LBUTTONDOWN) // 左鍵:顯示/隱藏主視窗
{
if (IsWindowVisible()) ShowWindow(SW_HIDE);
else { ShowWindow(SW_SHOW); SetForegroundWindow(); }
}
else if (lParam == WM_RBUTTONDOWN) // 右鍵:彈出選單
{
CMenu menu; menu.LoadMenu(IDR_TRAY_MENU);
CMenu* pPopup = menu.GetSubMenu(0);
CPoint pt; GetCursorPos(&pt);
SetForegroundWindow(); // 修正選單無焦點問題
pPopup->TrackPopupMenu(TPM_LEFTALIGN, pt.x, pt.y, this);
}
return 0;
}
| ID | 間隔 | 用途 | 關鍵邏輯 |
|---|---|---|---|
| 1 | 1000 ms | 時段排程檢查 | 比對系統時間與配置 (早/中/晚 開始時間),符合則載入目錄、播放、啟動 ID3 |
| 2 | 1000 ms | 播放進度更新 | m_nPos++、更新進度條/時間顯示、偵測單曲結束 → 自動下一曲/循環 |
| 3 | 配置分鐘 × 60000 | 時段持續時間 | 時間到 → 關閉音訊、殺掉 ID2/ID3、重啟 ID1 等待下一時段 |
| 4 | 100 ms (一次性) | 啟動隱藏主視窗 | ShowWindow(SW_HIDE)、KillTimer(4) |
void CMp3PlayerDlg::OnTimer(UINT nIDEvent)
{
if (nIDEvent == 1) // 排程檢查
{
if (!m_bAutoRun) return;
if (!m_ConfigFile.Open()) return;
m_ConfigFile.ReadDayInfo(currentWeekDay, m_DayInfo);
m_ConfigFile.Close();
SYSTEMTIME st; GetLocalTime(&st);
// 早班時段
if (st.wHour == MorningStartHour && st.wMinute == MorningStartMin)
{
LoadCatalogMusic(m_DayInfo.sSMusic); // 掃描目錄填入 m_MusicAry
Play(0);
KillTimer(1);
SetTimer(3, MorningDurationMin * 60000, NULL);
m_nFlag = 1; return;
}
// 中班、晚班邏輯同理...
}
else if (nIDEvent == 2) // 進度更新
{
if (m_Music.IsOpen() && !m_bPause)
{
m_nPos++;
UpdateProgressUI();
if (m_nPos >= m_nTime) // 單曲結束
{
if (m_nPlay == m_MusicAry.GetSize() - 1) // 最後一首
{
if (m_bAutoRun && m_CycleEnabled) // 循環模式
{ m_nPlay = 0; Play(0); }
else // 非循環或手動模式
{ StopAllTimers(); }
}
else // 下一首
{ Play(++m_nPlay); }
}
}
}
else if (nIDEvent == 3) // 時段結束
{
if (m_Music.IsOpen()) { m_Music.Close(); StopAllTimers(); SetTimer(1, 1000, NULL); }
}
else if (nIDEvent == 4) // 啟動隱藏
{ ShowWindow(SW_HIDE); KillTimer(4); }
COwnerDrawDialog::OnTimer(nIDEvent);
}
下篇將詳細介紹:
CConfigFile 實作。LoadCatalogMusic) 支援子目錄與多媒體副檔名。COwnerDrawDialog):橢圓按鈕、漸層標題列、自繪進度條。setaudio 指令封裝。