具有自动恢复功能的通知栏图标控件

——谨以怀念写Delphi的青春岁月

Posted by eagleboost on August 9, 2004

本文转载自我2004年在csdn发布的博客

前言

任务栏(Taskbar)是微软公司在Windows 95中引入的一种特殊的桌面工具条,它为用户快速访问计算机资源提供了极大的方便,而状态栏(以下称通知栏)无疑是任务栏上较为特殊的一个窗口。编程人员可以调用API函数Shell_NotifyIcon向通知栏发送消息来添加、删除或修改图标,当在图标上发生鼠标或键盘事件时,系统会向应用程序发送编程时预先定义的消息,通知栏处理回调函数就会被自动调用以做出相应的处理。实现上述功能的相关文章俯仰即拾,此处不再赘述。本文将讨论两个较为深入的问题及其在Delphi中的实现方法。

  1. Windows发生错误导致外壳Explorer.exe重启时通知栏图标的自动恢复
  2. 将自动恢复功能封装在控件中以便其它程序中调用

外壳Explorer重启时通知栏图标的自动恢复

相信很多Windows用户都碰到过这种情况:运行某个程序时出现意外错误,导致外壳程序Explorer.exe崩溃而发生重启(即Explorer.exe被关闭后重新运行),任务栏也在消失后重新生成,但应用程序在通知栏添加的图标消失了,虽然这些程序仍在运行,但再也无法通过通知栏图标与用户交互。为避免这种情况出现,Windows提供了相应的机制。在安装了Internet Explorer 4.0及以上版本的Windows操作系统中,当任务栏建立后,外壳会向所有顶层的应用程序发出通知消息,该消息是外壳以字符串”TaskbarCreated”为参数向系统注册获得的,应用程序窗口接收到该消息后就应该重新添加的通知栏图标。

在Delphi中实现过程如下:

1) 定义一个整型变量Msg`TaskbarRestart1,用以保存任务栏重建的消息。

2) 在主程序的initialization部分或者是在OnCreate事件中以”TaskbarCreated”为参数向系统注册消息(也即是询问”TaskbarCreated“是哪条消息,因为以相同的参数注册会得到相同的消息,而”TaskbarCreated“在Windows启动的时候就已经被外壳注册)。

1
2
initialization
  Msg`Taskbar`Restart := RegisterWindowMessage('`Taskbar`Created');

3) 重载主窗口的消息处理过程,拦截任务栏重建消息,进行重新添加图标的操作。

1
2
3
4
5
6
7
8
9
10
11
12
//重载主窗口的消息处理过程,拦截任务栏重建消息,进行重新添加图标的操作。
procedure TMainForm.WndProc(var Message: TMessage);
begin
  ……
  if Message.Msg = Msg``Taskbar``Restart then
  begin
    TrayIcon.Active := False;                  //删除通知栏图标
    TrayIcon.Active := True;                   //添加通知栏图标
  end;
  ……
  inherited WndProc(Message);
end; //end of WndProc

自动恢复功能的封装

由于外壳只向所有顶层的应用程序发送通知,这为封装自动恢复功能带来了一定的困难。因为通知栏图标的回调函数只能接收WM_XBUTTONDOWNWM_XBUTTONUP等有限的几个消息,并不能接收所有的窗口消息。本节介绍的方法将使得在控件中能够接收窗口消息,从而实现自动恢复功能的封装。

解决问题的关键是SetWindowLong函数,向它传入GWL_WNDPROC参数,可以改变一个窗口的窗口过程。只需在创建控件时将应用程序窗口的窗口过程指针保存起来,并指向为控件中的某个新的窗口处理过程,在控件中就能够响应所有的窗口消息了(包括任务栏重建的消息);当控件销毁的时候再将保存的原始窗口过程指针恢复即可。实现代码如下(其中”……”的地方略去容易实现的添加、删除通知栏图标等函数及过程):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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  
  //注册询问任务栏重建的消息  
  Msg`Taskbar`Created := RegisterWindowMessage('TaskbarCreated');
end.