任務欄(Taskbar)是微軟公司在Windows 95中引入的一種特殊的桌面工具條,它為用戶快速訪問計算機資源提供了極大的方便,而狀態欄(以下稱通知欄)無疑是任務欄上較為特殊的一個窗口。編程人員可以調用API函數Shell_NotifyIcon向通知欄發送消息來添加、刪除或修改圖標,當在圖標上發生鼠標或鍵盤事件時,系統會向應用程序發送編程時預先定義的消息,通知欄處理回調函數就會被自動調用以做出相應的處理。實現上述功能的相關文章俯仰即拾,此處不再贅述。本文將討論兩個較為深入的問題及其在Delphi中的實現方法。
1、Windows發生錯誤導致外殼Explorer.exe重啟時通知欄圖標的自動恢復
2、將自動恢復功能封裝在控件中以便其它程序中調用。
關鍵詞:通知欄、窗口過程
1 外殼Explorer重啟時通知欄圖標的自動恢復
相信很多Windows用戶都碰到過這種情況:運行某個程序時出現意外錯誤,導致外殼程序Explorer.exe崩潰而發生重啟(即Explorer.exe被關閉後重新運行),任務欄也在消失後重新生成,但應用程序在通知欄添加的圖標消失了,雖然這些程序仍在運行,但再也無法通過通知欄圖標與用戶交互。為避免這種情況出現,Windows提供了相應的機制。
在安裝了Internet Explorer 4.0及以上版本的Windows操作系統中,當任務欄建立後,外殼會向所有頂層的應用程序發出通知消息,該消息是外殼以字符串"TaskbarCreated"為參數向系統注冊獲得的,應用程序窗口接收到該消息後就應該重新添加的通知欄圖標。
在Delphi中實現過程如下:
1). 定義一個整型變量MsgTaskbarRestart,用以保存任務欄重建的消息。
2). 在主程序的initialization部分或者是在OnCreate事件中以"TaskbarCreated"為參數向系統注冊消息(也即是詢問"TaskbarCreated"是哪條消息,因為以相同的參數注冊會得到相同的消息,而"TaskbarCreated"在Windows啟動的時候就已經被外殼注冊)。
initialization
MsgTaskbarRestart := RegisterWindowMessage('TaskbarCreated');
3). 重載主窗口的消息處理過程,攔截任務欄重建消息,進行重新添加圖標的操作。
procedure TMainForm.WndProc(var Message: TMessage);
begin
……
if Message.Msg = MsgTaskbarRestart then
begin
TrayIcon.Active := False; //刪除通知欄圖標
TrayIcon.Active := True; //添加通知欄圖標
end;
……
inherited WndProc(Message);
end; //end of WndProc
2 自動恢復功能的封裝
由於外殼只向所有頂層的應用程序發送通知,這為封裝自動恢復功能帶來了一定的困難。因為通知欄圖標的回調函數只能接收WM_XBUTTONDOWN、WM_XBUTTONUP等有限的幾個消息,並不能接收所有的窗口消息。本節介紹的方法將使得在控件中能夠接收窗口消息,從而實現自動恢復功能的封裝。
解決問題的關鍵是SetWindowLong函數,向它傳入GWL_WNDPROC參數,可以改變一個窗口的窗口過程。只需在創建控件時將應用程序窗口的窗口過程指針保存起來,並指向為控件中的某個新的窗口處理過程,在控件中就能夠響應所有的窗口消息了(包括任務欄重建的消息);當控件銷毀的時候再將保存的原始窗口過程指針恢復即可。實現代碼如下(其中"……"的地方略去容易實現的添加、刪除通知欄圖標等函數及過程):
TEoCSysTray = class(TComponent)
Private
……
FActive: boolean;
FParentWindow: TWinControl; //父窗口
FNewWndProc: Pointer; //新的父窗口過程指針
FPrevWndProc: Pointer; //原先的父窗口過程指針
FTaskBarCreated: TNotifyEvent; //任務欄重建事件
……
procedure SetActive(Value: boolean); //設置控件是否起作用
procedure HookParentForm; //替換父窗口的窗口過程
procedure UnHookParentForm; //還原父窗口的窗口過程
procedure HookWndProc(var AMsg: TMessage); //新的父窗口過程
protected
procedure DoTaskBarCreated; dynamic; //觸發任務欄重建事件
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Active: boolean read FActive write SetActive;
property OnTaskBarCreated: TNotifyEvent read FTaskBarCreated
write FTaskBarCreated;
implementation
type
THack = class(TWinControl); //用以訪問位於父窗口保護域的默認窗口處理過程
var
MsgTaskbarCreated : Integer; //由系統注冊的任務欄重建消息
constructor TEoCSysTray.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
……
FActive := false;
FNewWndProc := MakeObjectInstance(HookWndProc);//建立新的窗口過程指針
FPrevWndProc := nil;
if (AOwner <> nil) and (AOwner is TForm) then //獲得父窗口
FParentWindow := TWinControl(AOwner)
else
FParentWindow := Application.MainForm;
……
end;//end of Contructor
destructor TEoCSysTray.Destroy;
begin
……
FDestroying := True;
FParentWindow := nil;
FreeObjectInstance(FNewWndProc);
FNewWndProc := nil;
……
inherited Destroy;
end; //end of destructor
procedure TEoCSysTray.SetActive(Value: boolean);
begin
if Value <> FActive then
begin
FActive := Value;
if not (csDesigning in ComponentState) then //控件未處於設計狀態
case Value of
True:
begin
……
HookParentForm; //替換父窗口的窗口過程
……
end;
False:
begin
……
UnHookParentForm; //還原父窗口的窗口過程
……
end;
end;
end;
end; //end of procedure SetActive
procedure TEoCSysTray.HookParentForm; //替換父窗口的窗口過程
var
P : Pointer;
begin
if Assigned(FParentWindow) and
not ((csDesigning in FParentWindow.ComponentState) or
(csDestroying in FParentWindow.ComponentState) or FDestroying) then
begin
FParentWindow.HandleNeeded;
P := Pointer(GetWindowLong(FParentWindow.Handle, GWL_WNDPROC));
if (P <> FNewWndProc) then
begin
FPrevWndProc := P;
SetWindowLong(FParentWindow.Handle,
GWL_WNDPROC, LongInt(FNewWndProc)); //替換父窗口的窗口過程
end;
end;
end; //end of procedure HookParentForm
procedure TEoCSysTray.UnHookParentForm; //還原父窗口的窗口過程
begin
if Assigned(FParentWindow) then
begin
if Assigned(FPrevWndProc) and FParentWindow.HandleAllocated and
(Pointer(GetWindowLong(FParentWindow.Handle, GWL_WNDPROC)) = FNewWndProc) then
SetWindowLong(FParentWindow.Handle,
GWL_WNDPROC, LongInt(FPrevWndProc)); //還原父窗口的窗口過程
end;
FPrevWndProc := nil;
end; //end of procedure UnHookParentForm
procedure TEoCSysTray.HookWndProc(var AMsg: TMessage);
begin
if Assigned(FParentWindow) then
begin
with AMsg do
begin
if Msg = MsgTaskbarCreated then //接收到任務欄重建消息
DoTaskBarCreated; //觸發任務欄重建事件
if Assigned(FPrevWndProc) then //調用原窗口的窗口過程
Result := CallWindowProc(FPrevWndProc, FParentWindow.Handle,
Msg, WParam, LParam)
else
Result := CallWindowProc(THack(FParentWindow).DefWndProc,
FParentWindow.Handle, Msg, WParam, LParam);
if Msg = WM_DESTROY then //窗口正被銷毀
UnHookParentForm; //還原父窗口的窗口過程
end;
end;
end; //end of procedure HookWndProc
procedure TEoCSysTray.DoTaskBarCreated;
begin
…… //在這裡重新添加通知欄圖標
if Assigned(FTaskBarCreated) then
FTaskBarCreated(Self);
end; //end of procedure DoTaskBarCreated
initialization
//注冊詢問任務欄重建的消息
MsgTaskbarCreated := RegisterWindowMessage('TaskbarCreated');