本文转载自我2004年在csdn发布的博客
前言
任务栏(Taskbar
)是微软公司在Windows 95中引入的一种特殊的桌面工具条,它为用户快速访问计算机资源提供了极大的方便,而状态栏(以下称通知栏)无疑是任务栏上较为特殊的一个窗口。编程人员可以调用API函数Shell_NotifyIcon向通知栏发送消息来添加、删除或修改图标,当在图标上发生鼠标或键盘事件时,系统会向应用程序发送编程时预先定义的消息,通知栏处理回调函数就会被自动调用以做出相应的处理。实现上述功能的相关文章俯仰即拾,此处不再赘述。本文将讨论两个较为深入的问题及其在Delphi中的实现方法。
- Windows发生错误导致外壳Explorer.exe重启时通知栏图标的自动恢复
- 将自动恢复功能封装在控件中以便其它程序中调用
外壳Explorer重启时通知栏图标的自动恢复
相信很多Windows用户都碰到过这种情况:运行某个程序时出现意外错误,导致外壳程序Explorer.exe崩溃而发生重启(即Explorer.exe被关闭后重新运行),任务栏也在消失后重新生成,但应用程序在通知栏添加的图标消失了,虽然这些程序仍在运行,但再也无法通过通知栏图标与用户交互。为避免这种情况出现,Windows提供了相应的机制。在安装了Internet Explorer 4.0及以上版本的Windows操作系统中,当任务栏建立后,外壳会向所有顶层的应用程序发出通知消息,该消息是外壳以字符串”Taskbar
Created”为参数向系统注册获得的,应用程序窗口接收到该消息后就应该重新添加的通知栏图标。
在Delphi中实现过程如下:
1) 定义一个整型变量Msg`TaskbarRestart1,用以保存任务栏重建的消息。
2) 在主程序的initialization部分或者是在OnCreate事件中以”Taskbar
Created”为参数向系统注册消息(也即是询问”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_XBUTTONDOWN
、WM_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.