《系統(tǒng)托盤編程完全指南(一)》由會員分享,可在線閱讀,更多相關《系統(tǒng)托盤編程完全指南(一)(7頁珍藏版)》請在裝配圖網(wǎng)上搜索。
1、文檔供參考,可復制、編制,期待您的好評與關注!
系統(tǒng)托盤編程完全指南(一)
編譯/northtibet
??? 自從Windows 95面市以來,系統(tǒng)托盤應用作為一種極具吸引力的UI深受廣大用戶的喜愛。使用系統(tǒng)托盤UI的Windows應用程序數(shù)不勝數(shù),比如"金山詞霸"、"Winamp"、"RealPlayer"等等。那么如何編寫自己的托盤應用呢?本文是系列文章中的第一篇,這些文章將比較系統(tǒng)地描述托盤應用的編程。并創(chuàng)建自己的C++類來增強系統(tǒng)托盤應用的特性。讀完這些文章,再參照例子,相信讀者能輕松自如地在自己的程序中應用系統(tǒng)托盤。
??? 大家知道,MFC框架沒有提供任何現(xiàn)成的類應用
2、于系統(tǒng)托盤UI,那么如何將表示應用程序的圖標添加到任務欄中呢?方法很簡單,只用到一個API函數(shù),它就是Shell_NotifyIcon。這個函數(shù)本身也相當容易理解和使用??纯此脑途椭懒耍?
BOOL Shell_NotifyIcon(
DWORD dwMessage,
PNOTIFYICONDATA pnid
);
第一個參數(shù)dwMessage類型為DWORD,表示要進行的動作,它可以是下面的值之一:
NIM_ADD: 添加一個圖標到任務欄。
NIM_MODIFY: 修改狀態(tài)欄區(qū)域的圖標。
3、 NIM_DELETE: 刪除狀態(tài)欄區(qū)域的圖標。
NIM_SETFOCUS: 將焦點返回到任務欄通知區(qū)域。當完成用戶界面操作時,任務欄圖標必須用此消息。例如,如果任務欄圖標正
顯示上下文菜單,但用戶按下"ESCAPE"鍵取消操作,這時就必須用此消息將焦點返回到任務欄通知區(qū)域。
NIM_SETVERSION:指示任務欄按照相應的動態(tài)庫版本工作。
第二個參數(shù)pnid是NOTIFYICONDATA結(jié)構(gòu)的地址,其內(nèi)容視dwMessage的值而定。這個結(jié)構(gòu)在SHELLAPI.H文件中定義如下:
4、
typedef struct _NOTIFYICONDATA {
DWORD cbSize; // 結(jié)構(gòu)大?。╯izeof struct),必須設置
HWND hWnd; // 發(fā)送通知消息的窗口句柄
UINT uID; // 圖標ID ( 由回調(diào)函數(shù)的WPARAM 指定)
UINT uFlags;
UINT uCallbackMessage; // 消息被發(fā)送到此窗口過程
HICON hIcon; // 圖標句柄
CHA
5、R szTip[64]; // 提示文本
} NOTIFYICONDATA;
uFlags的值:
#define NIF_MESSAGE 0x1 // 表示uCallbackMessage 有效
#define NIF_ICON 0x2 // 表示hIcon 有效
#define NIF_TIP 0x4 // 表示szTip 有效
有關Shell_NotifyIcon函數(shù)的詳細使用細節(jié)請參考MSDN。
??? NOTIFYICONDATA結(jié)構(gòu)中的 hWnd 是"擁有" 圖標的窗口句柄。uID可以是任何標示托盤圖標的ID(如果
6、有多個圖標),一般使用資源ID。HIcon可以是任何圖標的句柄,包括預定義的系統(tǒng)圖標,如IDI_HAND、IDI_QUESTION、IDI_EXCLAMATION、或者Windows的徽標IDI_WINLOGO。
??? 圖標的顯示并不難,關鍵是事件的處理。 當用戶將鼠標移到圖標上或者在圖標上單擊鼠標時,為了得到通知消息,你可以將自己的消息ID賦給uCallbackMessage,并設置NIF_MESSAGE標志。當用戶在圖標上移動或單擊鼠標時,Windows將用hWnd指定的窗口句柄調(diào)用你建立的窗口過程;消息ID在uCallbackMessage中指定,uID的值即為wParam,lPar
7、am為鼠標事件,如WM_LBUTTONDOWN等。
??? 盡管Shell_NotifyIcon函數(shù)簡單實用。但它畢竟是個Win32 API,為此我將它封裝在了一個C++類中,這個類叫做CTrayIcon,有了它,托盤編程會更加輕松自如,因為它隱藏了NOTIFYICONDATA、消息代碼、標志以及所有那些你必須要看MSDN才能搞掂的繁瑣細節(jié)。CTrayIcon的定義以及實現(xiàn)細節(jié)請下載源代碼參考。CTrayIcon為程序員提供了一個更加友好的托盤編程接口,它除了對Shell_NotifyIcon函數(shù)進行打包之外,它還是一個迷你框架呢!之所以這么說,是因為按照Windows系統(tǒng)應用軟件界面指南所
8、提倡的原則(這個指南可以在MSDN中找到),這個類增強了托盤圖標的用戶界面行為。以下便是CTrayIcon最終實現(xiàn)的UI特性:
1、 托盤圖標應該有信息提示,也就是ToolTips。
2、 單擊右鍵應該彈出上下文菜單,這個菜單中應包含打開屬性頁的命令或者打開與圖標相關的其它窗口的命令。
3、 單擊左鍵應該顯示進一步的信息或者控制圖標所代表的對象,例如,當左鍵單擊聲音圖標時進行音量控制。如果沒有進一步的信息或控制,則不要有任何動作。
??? CTrayIcon對上面的特性進行了全面的封裝。為了示范CTrayIcon的工作原理,本文提供一個例子程序TrayTest1,圖一是運行程序
9、后顯示的一個對話框:
圖一 TrayTest1運行后顯示的對話框
當把圖標安裝到系統(tǒng)托盤之后,如果雙擊托盤圖標,程序會彈出一個消息列表窗口,只要你的鼠標在托盤圖標上移動或點擊(無論是左右鍵的單擊或雙擊),產(chǎn)生的消息都會顯示在這個窗口里,如圖二:
圖二 消息顯示窗口
當鼠標光標移到托盤圖標上時,在圖標附近會顯示提示信息,如圖三:
圖三 顯示Tooltip
為了正確使用CTrayIcon,首先你必須在程序的某個地方實例化CTrayIcon,例子程序是在主框架中創(chuàng)建CTrayIcon實例的。
Class MainFrame public
10、CFrameWnd {protected: CTrayIcon m_trayIcon; // my tray icon
…….
};
??? 然后,你必須提供一個ID。這是在圖標生命期內(nèi)的唯一標示,即便以后你修改了要顯示的圖標。這個ID也是鼠標事件發(fā)生時你將獲得的ID。它不一定必須是圖標的資源ID,例子程序中這個ID為IDR_TRAYICON,由框架的構(gòu)造函數(shù)CMainFrame通過成員初始化列表對m_trayIcon進行初始化:
CMainFrame::CMainFrame() : m_trayIcon(IDR_TRAYICON)
11、{
……
}
為了添加圖標,必須根據(jù)具體情況調(diào)用下列的 SetIcon 函數(shù)之一:
m_trayIcon.SetIcon(IDI_MYICON); //資源 ID
m_trayIcon.SetIcon("myicon"); //資源名
m_trayIcon.SetIcon(hicon); //HICON
m_trayIcon.SetStandardIcon(IDI_WINLOGO);//系統(tǒng)圖標
??? 除了SetIcon(UINT uID)之
12、外,這些函數(shù)都有一個LPCSTR類型的可選參數(shù)用于指定提示文本。SetIcon(UINT uID)使用ID與uID相同的串資源作為提示文本。例如,TrayTest1有一行代碼是這樣的:
// (在mainframe.cpp文件中)
m_trayIcon.SetIcon(IDI_MYICON);
這行代碼也設置了提示信息,因為TrayTest1有一個串資源,其ID也是IDI_MYICON。這在TRAYTEST.RC文件中可以看到:
STRINGTABLE PRELOAD DISCARDABLE
BEGIN
IDI_MYICON "雙擊圖標激活 TRAYT
13、EST."
END
??? 如果你想改變圖標,可以用不同的ID或者HICON再次調(diào)用SetIcon函數(shù)之一。CTrayTest便會用NIM_MODIFY而不是NIM_ADD來改變圖標。相同的函數(shù)甚至可以用于刪除圖標,如:
m_trayIcon.SetIcon(0); //刪除圖標
??? CTrayIcon將此代碼解釋成NIM_DELETE。你已經(jīng)看到,所有這些表示行為的編碼,標志都被一個使用方便的函數(shù)所替代:這都歸功于C++!現(xiàn)在,我們來看看如何處理通知消息以及前面提到的所有UI特性。通知消息的處理必須要設置圖標之前,但是要在創(chuàng)建窗口之后調(diào)用CTrayIc
14、on::SetNotificationWnd,做這件事情的最佳場所是在OnCreate處理例程中,TrayTest就是在這里處理的:
// 注冊用于托盤的自定義消息
#define WM_MY_TRAY_NOTIFICATION WM_USER+0
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
……
// 請通知我
m_trayIcon.SetNotificationWnd(this,
WM_MY_TRAY_NOTIFICATION);
15、 m_trayIcon.SetIcon(IDI_MYICON);
return 0;
}
消息一旦注冊,接下來你便可以用通常的消息映射方式處理托盤通知消息。
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_MESSAGE(WM_MY_TRAY_NOTIFICATION,
OnTrayNotification)
// (or ON_REGISTERED_MESSAGE)
END_MESSAGE_MAP()
LRESULT
CMai
16、nFrame::OnTrayNotification(WPARAM wp, LPARAM lp)
{
……
// 顯示消息
……
return m_trayIcon.OnTrayNotification(wp, lp);
}
??? 當消息處理器得到控制,WPARAM的值是在構(gòu)造CTrayIcon時指定的ID;LPARAM為鼠標事件(如WM_LBUTTONDOWN)。當你得到通知消息后,可以做任何想做的的事情;例子程序TrayTest此時是顯示通知信息,細節(jié)請參考源代碼。完成消息的處理之后,調(diào)用CTrayIcon:
17、:OnTrayNotification進行缺省處理。此虛擬函數(shù)(所以你可以改寫)實現(xiàn)我前面提到過的缺省的UI行為。尤其是處理WM_LBUTTONDBLCLK和WM_RBUTTONUP。CTrayIcon尋找與圖標ID相同的某個菜單(如IDR_TRAYICON),如果找到,則當用戶右鍵單擊圖標時CTrayIcon顯示這個菜單;當用戶數(shù)雙擊圖標時,CTrayIcon執(zhí)行第一個菜單命令。只有兩件事情需要進一步交待:
??? 第一件事情是:在顯示菜單之前,CTrayIcon讓第一個菜單項為默認,所以它以黑體顯示。但如何用黑體來顯示某個菜單項呢?我在\MSDEV\INCLUDE\*.H搜索了一番,發(fā)現(xiàn)
18、了Get/SetMenuDefaultItem。這個函數(shù)沒有相關的CMenu打包類,所以我必須直接調(diào)用它們。
// 讓第一個菜單項為默認(黑體):
::SetMenuDefaultItem(pSubMenu->m_hMenu, 0, TRUE);
??? 這里0表示第一個菜單項,TRUE說明用位置表示菜單項的ID。為什么MFC沒有打包Get/SetMenuDefaultItem函數(shù)呢?微軟的家伙們解釋那是因為這些函數(shù)(其它的還有::Get/SetMenuItemInfo, ::LoadImage等)還沒有在最新的Windows版本中實現(xiàn)。一旦在最新的Windows版本中實
19、現(xiàn)了,便會馬上添加到MFC中。
??? 第二件事情是上下文菜單的顯示:
::SetForegroundWindow(m_nid.hWnd); ::TrackPopupMenu(pSubMenu->m_hMenu, ...);
??? 為了讓TrackPopupMenu在托盤的上下文中正確運行,你必須首先調(diào)用SetForegroundWindow,否則,當用戶按下ESCAPE鍵或者在菜單之外單擊鼠標時,菜單不會消失。為解決這個問題,我花費了數(shù)個小時,最后還是在MSDN上找到了解決方法。為了解詳情,請參考MSDN的Q135788。最讓我哭笑不得的是我花了那么多時間來
20、關注這個問題,最后微軟的這幫家伙在MSDN上給你來了一個問題的結(jié)論是:“This behavior is by design.....”真是氣剎人也。
??? 正如你所看到的,CTrayIcon使得托盤應用的編程變得易如反掌。TrayTest1要做的事情不外乎調(diào)用CTrayIcon::OnTrayNotification實現(xiàn)一個通知消息處理器,提供一個與圖標ID相同的菜單。就這么簡單。
// (TRAYTEST.RC文件)
IDR_TRAYICON MENU DISCARDABLE
BEGIN
POPUP "托盤(&T)"
BEGIN
MENU
21、ITEM "打開(&O)", ID_APP_OPEN
MENUITEM "關于 TrayTest(&A)...", ID_APP_ABOUT
MENUITEM SEPARATOR
MENUITEM "退出TrayTest 程序(&S)", ID_APP_SUSPEND
END
END
當用戶在托盤圖標上單擊右鍵,CTrayIcon顯示這個菜單,如圖四所示。如果用戶雙擊圖標,CTrayIcon執(zhí)行第一個菜單命令:“打開”,此時激活TrayTest(正常狀
22、態(tài)下是隱藏的)。為了終止TrayTest1,你必須選擇"Suspend TRAYTEST"菜單項。如果你從“文件|退出”退出,或者關閉TrayTest1主窗口,TrayTest1不會真正關閉,它只是將自己隱藏起來。這個行為是TrayTest1改寫了CMainframe::OnClose實現(xiàn)的。
圖四 TRAYTEST1 托盤圖標菜單
??? 最后,我想說明一個很讓人擔心的問題,每個人在看到這個小圖標后都想盡快的在自己的程序中加入托盤圖標。作為程序員,這完全是可以理解的。當自己的程序中成功添加了托盤圖標,在朋友們中間炫耀一番,那種感覺確實很好。但是要記住:并不是所有的應用都需要
23、用托盤圖標,如果不是必須就不要畫蛇添足,否則托盤圖標太多必然造成屏幕垃圾,看看下面圖五吧:
圖五 托盤圖標程序“噩夢版”
看到這么多的托盤圖標對于用戶來說簡直就是噩夢。(待續(xù))
我按照上面介紹的步驟去作,為什么在我點擊界面右上角的關閉按鈕,桌面右下角的圖標也一并消失了,整個就是完全退出.但是例子中的不是會仍有圖標顯示在桌面右下角嗎?請指點^_^ ( dahui11 發(fā)表于 2004-7-2 9:52:00)
?
可是就算是注銷掉也編譯ok啊。
但是啟動界面和程序界面同時出現(xiàn),這個問題怎樣解決? ( 艾葭 發(fā)表于 2003-8-11 9:08:00)
?
24、把GetSystemWindowsDirectory改成GetSystemDirectory ( liron 發(fā)表于 2002-12-25 8:21:00)
?
調(diào)試的時候會出錯,提示
D:...\StatLink.cpp(179)?:?error?C2065:?'GetSystemWindowsDirectory'?:?undeclared?identifier??Error?executing?cl.exe.
把GetSystemWindowsDirectory改成GetWindowsDirectory
就ok了。 ( seaboyf 發(fā)表于 2002-12-24 16:51:00)
?
7 / 7