想必大家都看見過那些在系統托盤(Tray)中的程序吧,本文就演示了如何創建一個這樣的托盤程序Alert。Alert是一個運行在系統托盤中的小鬧鐘,它在設定的時間後觸發,運行屏幕保護程序並播放一首歌曲。這個程序是很久以前寫的了,而我現在沒有安裝VC++了,所以本文的截圖都是朋友Leoyin幫我做出來後傳給我的(好麻煩,呵呵),在此也對他表示感謝!
建立一個托盤程序的關鍵在於Windows API-Shell_NotifyIcon()的使用,這個API的聲明包含在ShellAPI.H文件中。請在StdAfx.h中使用“#include <ShellAPI.H>”將其引入工程,別象我當時那麼傻-在工程管理器裡引入了該文件,導致現在你們在類視圖中看見了許多不希望看到的東西。我現在也沒有VC++ IDE來修改它了,就這樣吧。
首先,給大家介紹一下相關的函數和結構:Shell_NotifyIcon()的原型和說明(最新版)如下:
BOOL Shell_NotifyIcon(
參數及返回值說明:
DWORD dwMessage,
PNOTIFYICONDATA lpdata
);
dwMessage:[輸入參數] 說明要執行的動作。動作的可選值如下:
NIM_ADD 增加一個圖標到托盤區
NIM_DELETE 從托盤區刪除一個圖標
NIM_MODIFY 修改圖標
NIM_SETFOCUS 將焦點(Focus)返回托盤區。這個消息通常在托盤區圖標完成了用戶界面下的操作後發出。比如一個托盤圖標顯示了一個快捷菜單,然後用戶按下ESC鍵了操作,這時使用NIM_SETFOCUS將焦點繼續保留在托盤區。該項僅在系統外殼與常用控制DLL( Shlwapi.dll與Comctl32.dll)5.0以上版本才可用。
NIM_SETVERSION 指定使用特定版本的系統外殼與常用控制DLL。缺省值為0,表示使用Win95方式。該項僅在系統外殼與常用控制DLL 5.0以上版本才可用。
lpdata:[輸入參數] 一個指向NOTIFYICONDATA結構的指針。
返回值:成功時函數返回TRUE,否則FALSE。
NOTIFYICONDATA 結構的說明(最新版)如下:
typedef struct _NOTIFYICONDATA {
DWORD cbSize;
HWND hWnd;
UINT uID;
UINT uFlags;
UINT uCallbackMessage;
HICON hIcon;
TCHAR szTip[64];
DWORD dwState;
DWORD dwStateMask;
TCHAR szInfo[256];
union {
UINT uTimeout;
UINT uVersion;
};
TCHAR szInfoTitle[64];
DWORD dwInfoFlags;
GUID guidItem;
} NOTIFYICONDATA, *PNOTIFYICONDATA;
cbSize 以字節計的結構大小,以適應不同版本。 hWnd 接收Windows消息的窗口句柄。 uID 托盤圖標的ID。 uFlags 指示結構中的哪些成員包含有效數據,可選值:NIF_ICON, NIF_MESSAGE,NIF_TIP,NIF_STATE,NIF_INFO,NIF_GUID。 uCallbackMessage 回調消息ID,由用戶自定義。與一個自定義的消息處理函數關聯。 hIcon 托盤圖標的句柄。 szTip 托盤圖標的提示字符串。
注意:以下數據成員僅限系統外殼與常用控制DLL 5.0及以上版本才有效!
dwState 圖標的狀態:NIS_HIDDEN-隱藏,或NIS_SHAREDICON-可視。 dwStateMask 圖標狀態掩碼,用以設置dwState szInfo 氣球型提示(Balloon ToolTip)的字符串。 uTimeout 以毫秒計的提示顯示時間 uVersion 確定所依賴的版本。0-Win95,NOTIFYICON_VERSION-Win2000 szInfoTitle 氣球型提示的標題 dwInfoFlags設置氣球型提示所用的圖標(類似MessageBox中所使用的圖標):
NIIF_ERROR 錯誤
NIIF_INFO 信息
NIIF_NONE 沒有圖標
NIIF_WARNING 警告
NIIF_ICON_MASK 6.0版本保留
NIIF_NOSOUND 限6.0版本,不播放對應的聲音
guidItem 6.0版本保留接下來,讓我們開始實現這個Alert吧。Alert是一個基於對話框的Win32應用程序,利用AppWizard 創建它吧。然後設計一下這個對話框如圖:
在AlertDlg.H中加入:
#include < mmsystem.h > //這個頭文件是干什麼的記不得了,呵呵
#define WM_NOTIFYICON WM_USER+5 //自定義消息的ID
#define IDI_ICON 0x0005 //圖標ID
#define IDT_APPLY WM_USER+6 //我要使用的定時器Timer的自定義消息ID
然後為類CAlertDlg添加數據成員m_Interval和自定義成員函數的聲明(怎麼添加不用我教了吧?)。最後形成CAlertDlg類的聲明(已去掉了一些注釋)如下: class CAlertDlg : public CDialog
以下是AlertDlg.CPP中相應函數的實現(還是設置點錨點吧):
{
public:
~CAlertDlg();
CAlertDlg(CWnd* pParent = NULL);
enum { IDD = IDD_ALERT_DIALOG };
int m_Interval; //定時間隔,以分鐘為單位
protected:
virtual void DoDataExchange(CDataExchange* pDX);
protected:
void ShowMessage(void); //自定義的定時觸發時運行的函數
HICON m_hIcon; //圖標句柄
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg void OnClickApply(); //"應用"按鈕的點擊消息處理函數
virtual void OnCancel(); //"退出"按鈕的點擊消息處理函數
afx_msg void OnTimer(UINT nIDEvent); //定時器消息處理函數
afx_msg void OnClickHide(); //"隱藏"按鈕的點擊消息處理函數
//自定義WM_NOTIFYICON消息的處理函數
afx_msg void OnNotifyIcon(WPARAM wParam, LPARAM lParam);
afx_msg void OnSysCommand(UINT nID, LPARAM lParam); //系統消息處理函數
afx_msg BOOL OnQueryEndSession(); //Windows關閉消息處理函數
DECLARE_MESSAGE_MAP()
};BOOL CAlertDlg::OnInitDialog()
{
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
// 初始化時間間隔
CSpinButtonCtrl * pSpin;
pSpin = (CSpinButtonCtrl *) GetDlgItem(IDC_SPIN);
pSpin->SetRange(5,150);
m_Interval = 30;
CDialog::OnInitDialog();
// 設置定時器,每秒一次心跳
SetTimer(1, 1000, NULL);
// 將圖標放入系統托盤
NOTIFYICONDATA nd;
nd.cbSize = sizeof (NOTIFYICONDATA);
nd.hWnd = m_hWnd;
nd.uID = IDI_ICON;
nd.uFlags = NIF_ICON|NIF_MESSAGE|NIF_TIP;
nd.uCallbackMessage= WM_NOTIFYICON;
nd.hIcon = m_hIcon;
strcpy(nd.szTip, "我的鬧鐘");
Shell_NotifyIcon(NIM_ADD, &nd);
return TRUE;
}
void CAlertDlg::OnClickApply()
{
// 重置時間間隔
UpdateData();
OnTimer(IDT_APPLY);
}
void CAlertDlg::OnCancel()
{
// 釋放定時器
KillTimer(1);
// 將圖標從系統托盤中刪除
NOTIFYICONDATA nd;
nd.cbSize = sizeof (NOTIFYICONDATA);
nd.hWnd = m_hWnd;
Shell_NotifyIcon(NIM_DELETE, &nd);
CDialog::OnCancel();
}
void CAlertDlg::OnTimer(UINT nIDEvent)
{
static CString strTemp;
static int Count = 0;
COleDateTime dtTime;
// 刷新顯示的時間
//常在論壇裡遇到人問怎麼獲得當前時間,這相知道了吧?
dtTime = COleDateTime::GetCurrentTime();
strTemp.Format("現在時間 %02i:%02i:%02i ",
dtTime.GetHour(),
dtTime.GetMinute(),
dtTime.GetSecond());
GetDlgItem(IDC_CURTIME)->SetWindowText(strTemp);
// 刷新剩余時間值
if (nIDEvent == IDT_APPLY) Count = 0; //若用戶重新定義了時間間隔,則重新開始計時
Count++;
int RestTime;
RestTime = m_Interval - Count/60;
if (RestTime <= 0)
{
Count = 0;
ShowMessage();
}
strTemp.Format("離下次提醒還差 %i 分鐘", RestTime);
GetDlgItem(IDC_RESTTIME)->SetWindowText(strTemp);
CDialog::OnTimer(nIDEvent);
}
//這個ShowMessasge()函數,你們需要自己修改一下,我的代碼太死板了
void CAlertDlg::ShowMessage()
{
//調用C:\InfoCD\WinaMP\下的WinaMP3播放Music.m3u清單
::WinExec("C:\\INFOCD\\WINAMP\\WINAMP.EXE music.m3u",SW_HIDE);
MessageBox("您該休息一會兒了......",
"休息",
MB_SYSTEMMODAL|MB_OK|MB_ICONEXCLAMATION|MB_ICONWARNING);
//運行C:\Windows\System下的太空屏保
ShellExecute(m_hWnd,"open","C:\\WINDOWS\\SYSTEM\\太空.scr", NULL,NULL,SW_SHOWNORMAL);
}
afx_msg void CAlertDlg::OnNotifyIcon(WPARAM wParam, LPARAM lParam)
{
// 響應在托盤圖標上的單擊
//wParam中是響應消息的圖標ID,lParam中則是Windows的消息
if ((wParam == IDI_ICON)&&(lParam == WM_LBUTTONDOWN))
ShowWindow(SW_SHOWNORMAL);
}
void CAlertDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
// 屏蔽最大化(MFC Bug?),將最小化重定向至隱藏窗口
if (nID == SC_MAXIMIZE)
return;
if (nID == SC_MINIMIZE)
ShowWindow(SW_HIDE);
else
CWnd::OnSysCommand(nID, lParam);
}
BOOL CAlertDlg::OnQueryEndSession()
{
// 在用戶退出Windows時自動退出應用程序
CAlertDlg::OnCancel();
return TRUE;
}
void CAlertDlg::OnClickHide()
{
OnSysCommand(SC_MINIMIZE, 0x0000);
return;
}
好了,通過閱讀上面這些源代碼,我想大家對托盤程序的創建有了一定的認識了吧,這也是本文的根本目的。 只是這個程序有個Bug:在程序退出後圖標仍在托盤區顯示,要把鼠標在圖標上面過一下才會消失。這個Bug記得有人提出過解決辦法,好象是要釋放圖標句柄什麼的。可惜我記不得了,這個程序也沒有再修改。
衷心希望大家給我回復,謝謝!
本文配套源碼