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