任務(wù)欄(Taskbar)是微軟公司在Windows 95中引入的一種特殊的桌面工具條,它為用戶快速訪問計算機資源提供了極大的方便,而狀態(tài)欄(以下稱通知欄)無疑是任務(wù)欄上較為特殊的一個窗口。編程人員可以調(diào)用API函數(shù)Shell_NotifyIcon向通知欄發(fā)送消息來添加、刪除或修改圖標(biāo),當(dāng)在圖標(biāo)上發(fā)生鼠標(biāo)或鍵盤事件時,系統(tǒng)會向應(yīng)用程序發(fā)送編程時預(yù)先定義的消息,通知欄處理回調(diào)函數(shù)就會被自動調(diào)用以做出相應(yīng)的處理。實現(xiàn)上述功能的相關(guān)文章俯仰即拾,此處不再贅述。本文將討論兩個較為深入的問題及其在Delphi中的實現(xiàn)方法。
  1、Windows發(fā)生錯誤導(dǎo)致外殼Explorer.exe重啟時通知欄圖標(biāo)的自動恢復(fù)
  2、將自動恢復(fù)功能封裝在控件中以便其它程序中調(diào)用。
關(guān)鍵詞:通知欄、窗口過程
1 外殼Explorer重啟時通知欄圖標(biāo)的自動恢復(fù)
相信很多Windows用戶都碰到過這種情況:運行某個程序時出現(xiàn)意外錯誤,導(dǎo)致外殼程序Explorer.exe崩潰而發(fā)生重啟(即Explorer.exe被關(guān)閉后重新運行),任務(wù)欄也在消失后重新生成,但應(yīng)用程序在通知欄添加的圖標(biāo)消失了,雖然這些程序仍在運行,但再也無法通過通知欄圖標(biāo)與用戶交互。為避免這種情況出現(xiàn),Windows提供了相應(yīng)的機制。
在安裝了Internet Explorer 4.0及以上版本的Windows操作系統(tǒng)中,當(dāng)任務(wù)欄建立后,外殼會向所有頂層的應(yīng)用程序發(fā)出通知消息,該消息是外殼以字符串"TaskbarCreated"為參數(shù)向系統(tǒng)注冊獲得的,應(yīng)用程序窗口接收到該消息后就應(yīng)該重新添加的通知欄圖標(biāo)。
在Delphi中實現(xiàn)過程如下: 
1). 定義一個整型變量MsgTaskbarRestart,用以保存任務(wù)欄重建的消息。
2). 在主程序的initialization部分或者是在OnCreate事件中以"TaskbarCreated"為參數(shù)向系統(tǒng)注冊消息(也即是詢問"TaskbarCreated"是哪條消息,因為以相同的參數(shù)注冊會得到相同的消息,而"TaskbarCreated"在Windows啟動的時候就已經(jīng)被外殼注冊)。
initialization
  MsgTaskbarRestart := RegisterWindowMessage('TaskbarCreated');
3). 重載主窗口的消息處理過程,攔截任務(wù)欄重建消息,進(jìn)行重新添加圖標(biāo)的操作。
PRocedure TMainForm.WndProc(var Message: TMessage);
begin
  ……
  if Message.Msg = MsgTaskbarRestart then
  begin
    TrayIcon.Active := False;      //刪除通知欄圖標(biāo)
    TrayIcon.Active := True;       //添加通知欄圖標(biāo)
  end;
  ……
  inherited WndProc(Message);
end; //end of WndProc
2 自動恢復(fù)功能的封裝
由于外殼只向所有頂層的應(yīng)用程序發(fā)送通知,這為封裝自動恢復(fù)功能帶來了一定的困難。因為通知欄圖標(biāo)的回調(diào)函數(shù)只能接收WM_XBUTTONDOWN、WM_XBUTTONUP等有限的幾個消息,并不能接收所有的窗口消息。本節(jié)介紹的方法將使得在控件中能夠接收窗口消息,從而實現(xiàn)自動恢復(fù)功能的封裝。
解決問題的關(guān)鍵是SetWindowLong函數(shù),向它傳入GWL_WNDPROC參數(shù),可以改變一個窗口的窗口過程。只需在創(chuàng)建控件時將應(yīng)用程序窗口的窗口過程指針保存起來,并指向為控件中的某個新的窗口處理過程,在控件中就能夠響應(yīng)所有的窗口消息了(包括任務(wù)欄重建的消息);當(dāng)控件銷毀的時候再將保存的原始窗口過程指針恢復(fù)即可。實現(xiàn)代碼如下(其中"……"的地方略去容易實現(xiàn)的添加、刪除通知欄圖標(biāo)等函數(shù)及過程):
  TEoCSysTray = class(TComponent)
  Private
    ……
    FActive: boolean;
    FParentWindow: TWinControl;     //父窗口
    FNewWndProc: Pointer;     //新的父窗口過程指針
    FPrevWndProc: Pointer;     //原先的父窗口過程指針
    FTaskBarCreated: TNotifyEvent;     //任務(wù)欄重建事件
    ……
    procedure SetActive(Value: boolean);   //設(shè)置控件是否起作用
    procedure HookParentForm;     //替換父窗口的窗口過程
    procedure UnHookParentForm;     //還原父窗口的窗口過程
    procedure HookWndProc(var AMsg: TMessage);  //新的父窗口過程
  protected
    procedure DoTaskBarCreated; dynamic;   //觸發(fā)任務(wù)欄重建事件
  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);   //用以訪問位于父窗口保護域的默認(rèn)窗口處理過程
var
  MsgTaskbarCreated  : Integer;   //由系統(tǒng)注冊的任務(wù)欄重建消息
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 //控件未處于設(shè)計狀態(tài)
      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    //接收到任務(wù)欄重建消息
        DoTaskBarCreated;     //觸發(fā)任務(wù)欄重建事件
      if Assigned(FPrevWndProc) then     //調(diào)用原窗口的窗口過程
        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
  ……    //在這里重新添加通知欄圖標(biāo)
  if Assigned(FTaskBarCreated) then
    FTaskBarCreated(Self);
end; //end of procedure DoTaskBarCreated
initialization
  //注冊詢問任務(wù)欄重建的消息
  MsgTaskbarCreated := RegisterWindowMessage('TaskbarCreated');
end.
新聞熱點
疑難解答