西安二炮工程學(xué)院 俞俊軍 張 毅
摘要 本文對如何將應(yīng)用程序的圖標(biāo)加入到Windows的系統(tǒng)托盤中做了較為詳細(xì)的介紹, 然后給出了一個C++類以方便的實現(xiàn)該功能,并在VC++6.0中給出了一個應(yīng)用程序 實例來體現(xiàn)其具體實現(xiàn)過程。同時該應(yīng)用程序?qū)嵗講解了如何在托盤中實現(xiàn)動 畫圖標(biāo)以及在程序中關(guān)閉計算機(jī)的技術(shù)。
關(guān)鍵詞:系統(tǒng)托盤 動畫圖標(biāo)
Windows98桌面的系統(tǒng)托盤位于任務(wù)欄的右側(cè),即Windows98桌面的右下方。它常 用來顯示一些系統(tǒng)的狀態(tài)。如:系統(tǒng)時間,音量控制以及其它的一些圖標(biāo)(依個 人機(jī)器安裝的軟件而不定),如下圖為筆者的Windows98系統(tǒng)托盤。(圖略)
常常能見到一些優(yōu)秀的軟件在運(yùn)行后會將其應(yīng)用程序圖標(biāo)加入到系統(tǒng)托盤中,如 金山詞霸。如果能將自己編寫的應(yīng)用程序的圖標(biāo)也加入到系統(tǒng)托盤中,將會使你 的程序顯得很有專業(yè)水準(zhǔn)。
其實這并不困難,與系統(tǒng)托盤通信的函數(shù)只有一個:
Shell_NotifyIcon (UINT message, NOTIFYICONDATA &m_nid);
首先看一下該函數(shù)的兩個參數(shù)。 第一個參數(shù)message可以取以下值:
NIM_ADD 向托盤中加入一個圖標(biāo); NIM_MODIFY 修改托盤中的圖標(biāo) NIM_DELETE 從托盤中刪除一個圖標(biāo)
第二個參數(shù)m_nid是NOTIFYICONDATA結(jié)構(gòu)的一個引用。該結(jié)構(gòu)的原型如下: typedef struct _NOTIFYICONDATA { DWORD cbSize;// 結(jié)構(gòu)的大小,必須在程序中給出 HWND hWnd; //是你程序中將要接收托盤消息的窗口句柄 UINT uID; // 應(yīng)用程序中定義的托盤圖標(biāo)ID,此參數(shù)用作標(biāo)識 UINT uFlags; //設(shè)置屬性,低三位有意義,0--7,如下: //第一位//#define NIF_MESSAGE 0x1 // uCallbackMessage參數(shù)有效
//第二位//#define NIF_ICON 0x2 // hIcon參數(shù)有效 //第三位//#define NIF_TIP 0x4 // szTip參數(shù)有效 UINT uCallbackMessage; // 自定義的消息ID值,一定不要與以有的消息ID相重。 HICON hIcon; //顯示在系統(tǒng)托盤上的Icon的句柄,可以為系統(tǒng)的 IDI_WINLOGO等 CHAR szTip[64]; // 用于圖標(biāo)顯示的提示字符串 } NOTIFYICONDATA;
為了接收到來自托盤的通知消息你可以將uCallbackMessage設(shè)定為你所定義的消息 ID值,同時設(shè)定NIF_MESSAGE標(biāo)志。這樣當(dāng)用戶在你的托盤圖標(biāo)上移動或按下鼠標(biāo) 時,Windows將發(fā)出消息:該消息的 messageID是你在uCallbackMessage中定義的 值;wParam是你定義的uID值;而lParam是鼠標(biāo)事件(如WM_LBUTTONDOWN),這樣你 的應(yīng)用程序就能響應(yīng)該事件了。 因此,為了將自己的應(yīng)用程序加入到系統(tǒng)托盤中,首先得建立一處理托盤通知消息 的窗口對象,然后將窗口對象與你自己的托盤通知消息聯(lián)系起來并建立相應(yīng)的托盤 通知消息映射機(jī)制,以便你的窗口對象能處理相應(yīng)的事件。
可以看到結(jié)構(gòu)體NOTIFYICONDATA中,其成員變量hWnd,uID,uFlags均用于在窗口對 象與你自己的托盤通知消息之間建立聯(lián)系,而成員變量uCallbackMessage則必須是 對應(yīng)于你的窗口對象的托盤通知消息ID值。
于是要完成的工作有:
(1)建立一處理托盤通知消息的窗口對象; (2)建立一結(jié)構(gòu)體NOTIFYICONDATA變量,并給變量的相應(yīng)域賦值以在托盤通知消 息與窗口對象之間建立聯(lián)系; (3)建立相應(yīng)的托盤通知消息映射機(jī)制; (4)調(diào)用Shell_NotifyIcon函數(shù)以在系統(tǒng)托盤中加入、修改或刪除圖標(biāo); (5)當(dāng)然別忘了在你的窗口對象中編寫相應(yīng)的事件響應(yīng)函數(shù)。
因此,可以編寫一C++類來實現(xiàn)以上功能以簡化編程同時提高代碼的可重用性。以 下為該類代碼:
class CTrayIcon : public CCmdTarget { protected: DECLARE_DYNAMIC(CTrayIcon) NOTIFYICONDATA m_nid; // Shell_NotifyIcon 函數(shù)中的結(jié)構(gòu)參數(shù)
public: CTrayIcon(UINT uID); ~CTrayIcon();
// 通過調(diào)用該成員函數(shù)來接收托盤通知消息 void SetNotificationWnd(CWnd* pNotifyWnd, UINT uCbMsg);
// SetIcon 函數(shù)用來在系統(tǒng)托盤中加入、改變及刪除圖標(biāo)。 //要刪除圖標(biāo)這樣調(diào)用:SetIcon(0) BOOL SetIcon(UINT uID); BOOL SetIcon(HICON hicon, LPCSTR lpTip); BOOL SetIcon(LPCTSTR lpResName, LPCSTR lpTip) { return SetIcon(lpResName ? AfxGetApp()->LoadIcon(lpResName):NULL,lpTip); } BOOL SetStandardIcon(LPCTSTR lpszIconName,LPCSTR lpTip) { return SetIcon(::LoadIcon(NULL,lpszIconName),lpTip); }
virtual LRESULT OnTrayNotification(WPARAM uID, LPARAM lEvent); };
CTrayIcon::CTrayIcon(UINT uID) { //初始化NOTIFYICONDATA結(jié)構(gòu)變量 memset(&m_nid, 0 , sizeof(m_nid)); m_nid.cbSize = sizeof(m_nid); m_nid.uID = uID; AfxLoadString(uID, m_nid.szTip, sizeof (m_nid.szTip)); }
CTrayIcon::~CTrayIcon() { SetIcon(0); // 從系統(tǒng)托盤中刪除圖標(biāo) }
// 設(shè)定通知窗口,該窗口必須已被創(chuàng)建 void CTrayIcon::SetNotificationWnd(CWnd* pNotifyWnd, UINT uCbMsg) { ASSERT(pNotifyWnd==NULL || ::IsWindow(pNotifyWnd->GetSafeHwnd()));
m_nid.hWnd = pNotifyWnd->GetSafeHwnd();
ASSERT(uCbMsg==0 || uCbMsg>=WM_USER); m_nid.uCallbackMessage = uCbMsg; }
BOOL CTrayIcon::SetIcon(UINT uID) { HICON hicon=NULL; if (uID) { AfxLoadString(uID, m_nid.szTip, sizeof(m_nid.szTip)); hicon = AfxGetApp()->LoadIcon(uID); } return SetIcon(hicon, NULL); }
////////////////// // BOOL CTrayIcon::SetIcon(HICON hicon, LPCSTR lpTip) { UINT msg; m_nid.uFlags = 0;
// 設(shè)定圖標(biāo) if (hicon) { // 判斷是要在系統(tǒng)托盤中增加還是要刪除圖標(biāo) msg = m_nid.hIcon ? NIM_MODIFY : NIM_ADD; m_nid.hIcon = hicon; m_nid.uFlags |= NIF_ICON; } else { // 刪除圖標(biāo) if (m_nid.hIcon==NULL) return TRUE; //已被刪除 msg = NIM_DELETE; } if (lpTip) strncpy(m_nid.szTip, lpTip, sizeof(m_nid.szTip)); if (m_nid.szTip[0]) m_nid.uFlags |= NIF_TIP;
if (m_nid.uCallbackMessage && m_nid.hWnd) m_nid.uFlags |= NIF_MESSAGE;
BOOL bRet = Shell_NotifyIcon(msg, &m_nid); if (msg==NIM_DELETE || !bRet) m_nid.hIcon = NULL; return bRet; }
// 缺省事件處理程序,該程序處理鼠標(biāo)右擊及雙擊事件。 LRESULT CTrayIcon::OnTrayNotification(WPARAM wID, LPARAM lEvent) { if (wID!=m_nid.uID || (lEvent!=WM_RBUTTONUP && lEvent!=WM_LBUTTONDBLCLK)) return 0;
// 使用與托盤圖標(biāo)擁有同樣ID號的菜單作為右鍵彈出菜單 // 并將菜單上的第一項作為缺省命令使用, // 缺省命令在WM_LBUTTONDBLCLK事件發(fā)生時被擊發(fā) // CMenu menu; if (!menu.LoadMenu(m_nid.uID)) return 0; CMenu* pSubMenu = menu.GetSubMenu(0); if (!pSubMenu) return 0;
if (lEvent==WM_RBUTTONUP) {
//使菜單第一項為缺省項 (表現(xiàn)為粗體) ::SetMenuDefaultItem(pSubMenu->m_hMenu, 0, TRUE);
// 在鼠標(biāo)的當(dāng)前位置彈出菜單。 CPoint mouse; GetCursorPos(&mouse); ::SetForegroundWindow(m_nid.hWnd); ::TrackPopupMenu(pSubMenu->m_hMenu, 0, mouse.x, mouse.y, 0, m_nid.hWnd, NULL);
} else // 雙擊事件: 執(zhí)行菜單第一項 ::SendMessage(m_nid.hWnd, WM_COMMAND, pSubMenu-> GetMenuItemID(0), 0);
return 1; // 表示事件已被處理 }
以下以在VC++6.0中具體實現(xiàn)的程序為例。該程序?qū)碛幸韵鹿δ埽撼绦虮粓?zhí)行 后,首先顯示一對話框表示程序開始執(zhí)行,然后該對話框消失。接著程序圖標(biāo) 被加入到系統(tǒng)托盤中,可以看到,該圖標(biāo)將是一動畫圖標(biāo)。當(dāng)鼠標(biāo)在該系統(tǒng)托 盤上右擊時,將彈出一菜單。如圖所示(略)。其第一項為缺省項命令,單擊 將顯示應(yīng)用程序。為簡化編程,該應(yīng)用程序只是顯示一應(yīng)用程序主窗口。而單擊 菜單第二項將關(guān)閉機(jī)器,單擊菜單第三項將結(jié)束本程序。當(dāng)并且當(dāng)用戶雙擊時, CTrayIcon將執(zhí)行菜單上的第一項:顯示服務(wù)程序,這將擊活(顯示)TrayDemo (正常情況下,它是隱藏的)。而要終止TrayDemo,你得選擇結(jié)束本程序。當(dāng)你 執(zhí)行File Exit或關(guān)掉TrayDemo主窗口時,TrayDemo并沒有真正的關(guān)掉,它只不過 隱藏起來了而已。TrayDemo 重載了Cmainframe::OnClose函數(shù)以執(zhí)行該項功能。 首先在VC++6.0中生成用應(yīng)用程序向?qū)梢粏挝臋n工程TrayDemo,然后在工程中 加入以上的CTrayIcon類。
要使用CTrayIcon類,你首先得實例化一個CTrayIcon類對象,TrayDemo在視圖中 完成此項工作。以下是對應(yīng)代碼:
class CTrayDemoView : public CView { protected: CTrayIcon m_trayIcon; // my tray icon . . . };
當(dāng)你實例化一個CTrayIcon類對象之后,你必須分配給其一個ID號。該ID號是此圖 標(biāo)在其生命周期內(nèi)使用的唯一一個ID號,即使在以后你改變了實際顯示的圖標(biāo)。此 ID號是當(dāng)鼠標(biāo)事件發(fā)生時你獲得的ID。它可以不必是圖標(biāo)的資源ID;在TrayDemo 中,其值是IDR_TRAYICON,由CTrayDemoView構(gòu)造函數(shù)所初始化。 CTrayDemoView::CTrayDemoView() : m_trayIcon(IDR_TRAYICON){ . . . }
要增加圖標(biāo),可調(diào)用SetIcon重載函數(shù)之一 m_trayIcon.SetIcon(IDI_MYICON); //參數(shù)為資源ID m_trayIcon.SetIcon("myicon"); //參數(shù)為資源名 m_trayIcon.SetIcon(hicon); //參數(shù)為HICON句柄 m_trayIcon.SetStandardIcon(IDI_WINLOGO); //加入系統(tǒng)圖標(biāo)
除了SetIcon(UINT uID)函數(shù)需要一個同樣擁有uID號的字符串資源作為提示字符串 以外,所有這些函數(shù)都有一個可選的指向提示字符串的LPCSTR參數(shù)。例如,在 TRAYTEST中有以下行: // (In TrayDemoView.cpp) m_trayIcon.SetIcon(IDI_RED);
該語句在增加圖標(biāo)的同時同樣設(shè)定了提示字符串,因為TrayDemo有一個同樣ID的字 符串:如果你想改變圖標(biāo),只需再次調(diào)用其中的一個SetIcon函數(shù),只不過需要不 同的ID或HICON。CTrayIcon類知道響應(yīng)NIM_MODIFY消息而不是NIM_ADD消息。同樣 的函數(shù)甚至可以去掉圖標(biāo): m_trayIcon.SetIcon(0);//removeicon
CtrayIcon類會將其解釋為NIM_DELETE事件。這么多的代碼和標(biāo)志只用一個簡單的 重載函數(shù)就予以完成,這是C++的偉大之處。 如果要顯示動畫圖標(biāo),只需設(shè)置一定時器,然后在定時器的響應(yīng)事件中調(diào)用 SetIcon成員函數(shù)就可以了。如:
int CTrayDemoView::OnCreate(LPCREATESTRUCT lpCreateStruct) { m_timerID = this->SetTimer(99,200,NULL); … }
void CTrayDemoView::OnTimer(UINT nIDEvent) { uChangeIcon++; if(uChangeIcon-IDI_RED>2) uChangeIcon=IDI_RED; m_trayIcon.SetIcon(uChangeIcon); CView::OnTimer(nIDEvent); }
在示例程序中,有3個圖標(biāo),其ID為IDI_RED,IDI_YELLO,IDI_GREEN,且其ID值是相 連的,因而UINT型變量uChangeIcon用來依次輪換三個圖標(biāo)。這樣程序執(zhí)行以后,你 將會看到紅、黃、綠三個交通指示燈依次閃爍。 那么怎樣處理托盤通知呢?
要處理托盤通知,需要在你設(shè)定圖標(biāo)之前調(diào)用CTrayIcon::SetNotificationWnd函 數(shù),當(dāng)然你必須已經(jīng)創(chuàng)建了窗口。最適當(dāng)?shù)牡胤绞窃贠nCreate函數(shù)中,在TrayDemo 中也是這樣做的。用ClassWizard在CtrayDemoView類中加入WM_CREATE消息響應(yīng)函 數(shù)OnCreate(),并加入以下代碼:
// Private message used for tray notifications #define WM_MY_TRAY_NOTIFICATION WM_USER+0 int CTrayDemoView::OnCreate(LPCREATESTRUCT lpCreateStruct) . . . m_trayIcon.SetNotificationWnd(this,WM_MY_TRAY_NOTIFICATION); m_trayIcon.SetIcon(IDI_RED); return 0; }
然后進(jìn)行消息注冊(REGISTER),一旦注冊以后,你就可以用正常的消息映射方式 處理托盤通知。 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_MESSAGE(WM_MY_TRAY_NOTIFICATION,OnTrayNotification) // (or ON_REGISTERED_MESSAGE) END_MESSAGE_MAP()
當(dāng)然不要忘了在TrayDemoView.h中加入以下語句: afx_msg LRESULT OnTrayNotification(WPARAM wp, LPARAM lp);
當(dāng)你的處理程序得到在托盤圖標(biāo)上的鼠標(biāo)事件的控制以后,WPARAM參數(shù)是你在創(chuàng)建 CTrayIcon類時定義的ID;LPARAM是鼠標(biāo)事件(如,WM_LBUTTONDOWN)。當(dāng)捕獲到 通知后你可以做任何你想做的事情;記得最后要調(diào)用 CTrayIcon::OnTrayNotification函數(shù)以完成一些缺省的處理。該虛函數(shù)完成前面 所提到的一些缸省的UI行為。特別的,它處理WM_LBUTTONDBLCLK和WM-RBUTTONUP事 件。CTrayIcon類尋找與圖標(biāo)擁有同樣ID的菜單(如,IDR_TRAYICON)。如果擁有 該ID的菜單存在,CTrayIcon類將在用戶右擊圖標(biāo)的時候顯示此菜單;而當(dāng)用戶雙 擊時,CTrayIcon將執(zhí)行菜單上的第一個命令。
LRESULT CTrayDemoView::OnTrayNotification(WPARAM wp, LPARAM lp) { return m_trayIcon.OnTrayNotification(wp, lp); }
只有兩件事需要進(jìn)一步解釋。在顯示菜單之前,CTrayIcon類使得第一項為缸省項, 因此它看起來是大寫的。但怎樣使得一個菜單項大寫呢?使用函數(shù) GSetMenuDefaultItem。 // Make first menu item the default (bold font) ::SetMenuDefaultItem(pSubMenu->m_hMenu, 0, TRUE);
這里的0便指定了第一個菜單項,TRUE表示通過位置而不是ID來確定菜單項。 對CTrayIcon::OnTrayNotification,我們關(guān)心的第二項是為了顯示相關(guān)菜單,它干 了些什么?
::SetForegroundWindow(m_nid.hWnd); ::TrackPopupMenu(pSubMenu->m_hMenu, ...);
為了使TrackPopupMenu函數(shù)在托盤環(huán)境中工作正常,你必須首先在擁有該彈出菜單 的窗口中調(diào)用SetForegroundWindow函數(shù)。否則,當(dāng)用戶按下Esc鍵或在菜單以外單 擊鼠標(biāo)時該菜單將不會消失。正如你看到的那樣,CTrayIcon類使得托盤圖標(biāo)的編 程很簡單。為了使托盤菜單生效,在TrayDemo中所做的只是實現(xiàn)一個通知程序,在 該程序中調(diào)用了CTrayIcon::OnTrayNotification,對了別忘了還要提供一個與 CTrayIcon類擁有同樣ID的菜單。TrayDemo程序中是在菜單編輯器內(nèi)加入一ID為 IDR_TRAYICON的如下菜單: 然后,用ClassWizard在視圖類中分別為三個菜單命令加入如下的響應(yīng)函數(shù):
void CTrayDemoView::OnDisplayProgram() { CWnd* pWnd; pWnd=AfxGetApp()->m_pMainWnd; pWnd->ShowWindow(SW_NORMAL); pWnd->SetForegroundWindow(); }
void CTrayDemoView::OnCloseProgram() { m_bShutdown = TRUE; // really exit CWnd* pWnd; pWnd=AfxGetApp()->m_pMainWnd; pWnd->SendMessage(WM_CLOSE); }
void CTrayDemoView::OnShutoff() { ExitWindowsEx(EWX_SHUTDOWN,0); }
其中,在OnShutoff函數(shù)中,ExitWindowsEx(EWX_SHUTDOWN,0)用來關(guān)閉計算機(jī)。限 于篇幅,這里不作詳細(xì)介紹,讀者可以查看MSDN來獲得更詳細(xì)的資料。 最后,還要重載Cmainframe::OnClose函數(shù)如下:
void CMainFrame::OnClose() { CTrayDemoView *pView = (CTrayDemoView *)GetActiveView(); if (pView->m_bShutdown) CFrameWnd::OnClose(); else ShowWindow(SW_HIDE); }
提醒一點(diǎn),為使框架程序識別視圖類,還要在MainFrm.cpp中加入如下兩句: #include "TrayDemoDoc.h" #include "TrayDemoView.h"
如果有興趣,還可以對將本程序繼續(xù)擴(kuò)充,使之可以監(jiān)視系統(tǒng)的狀態(tài):當(dāng)鼠標(biāo)和鍵 盤在超過一設(shè)定的時間后,仍沒有動作,則程序?qū)⒆詣訄?zhí)行關(guān)機(jī)命令。 以上程序在Windows98,VC++6.0中調(diào)試通過。
|