西安二炮工程學院 俞俊軍 張 毅
摘要
本文對如何將應用程式的表徵圖加入到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_DYNAMIC(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_SHUTDOWN,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中調試通過。
---------------------------------------------------------------------------------------
| VC Tray Icon的使用 |
作者: 來自: 閱讀次數:
6 [大 中 小] |
| |
為CMainFrame增加如下的成員變數: NOTIFYICONDATA m_trayIcon; CMenu m_menuTray;在CMainFrame的OnCreate函數末尾增加: m_trayIcon.cbSize = sizeof(NOTIFYICONDATA); m_trayIcon.hIcon = (HICON)LoadImage(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDI_TRAYICON), IMAGE_ICON, 16, 16, 0); m_trayIcon.hWnd = m_hWnd; m_trayIcon.uID = MAINTRAYICON; m_trayIcon.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; m_trayIcon.uCallbackMessage = WM_TRAYICON; strcpy(m_trayIcon.szTip, LoadResString(IDS_TOOLTIP)); Shell_NotifyIcon(NIM_ADD, &m_trayIcon); 增加訊息WM_TRAYICON響應函數: ON_MESSAGE(WM_TRAYICON,OnTrayNotify) int CMainFrame::OnTrayNotify(WPARAM wParam, LPARAM lParam) { if(wParam != MAINTRAYICON) return 0L; if(m_menuTray.m_hMenu == NULL && !m_menuTray.LoadMenu(IDR_MAINFRAME)) return 0; CMenu* pSubMenu = m_menuTray.GetSubMenu(0); if (!pSubMenu) return 0; if (lParam == WM_RBUTTONUP) { ::SetMenuDefaultItem(pSubMenu->m_hMenu, 0, TRUE); CPoint pos; GetCursorPos(&pos); SetForegroundWindow(); pSubMenu->TrackPopupMenu(TPM_RIGHTALIGN|TPM_LEFTBUTTON |TPM_RIGHTBUTTON, pos.x, pos.y, this, NULL); } else if(lParam == WM_LBUTTONDOWN) { ShowWindow(SW_SHOW); SetForegroundWindow(); } else if(lParam == WM_LBUTTONDBLCLK) { SendMessage(WM_COMMAND, pSubMenu->GetMenuItemID(0), 0); } return 1L; } 如果同時還需要屏蔽視窗關閉按鈕的話需要增加訊息處理函數: ON_WM_SYSCOMMAND() void CMainFrame::OnSysCommand(UINT nID, LPARAM lParam) { if(nID == SC_CLOSE) { ShowWindow(SW_HIDE); return; } CFrameWnd::OnSysCommand(nID, lParam); } 在視窗的Destroy函數調用: Shell_NotifyIcon(NIM_DELETE, &m_trayIcon); |