本文由如何编程设置Taskbar的Auto Hide属性这个问题开始探讨Taskbar编程技巧问题.
Taskbar 编程技巧
我的计算机自从装上Windows 95以后,我开始使用那个叫做Taskbar的冬冬,但是从来也没有怎么注意和研究过这个冬冬,最多不过往上面加个小动画图标什么的(网上相关的信息泛滥)。但有一次在万千讨论组Delphi版(news://www.webking.com.cn)里面,网友问到一个刁钻的问题 -- 如何编程设置Taskbar的Auto Hide属性。
问题一提出来,我非常感兴趣(只是觉得好玩),我开始研究了这个平时没有注意的细微角落。我开始查阅MSDN的Taskbar小节的内容。结果却让我大为失望,没有MSDN的朋友可以参考下面的网址(http://msdn.microsoft.com/library/psdk/shellcc/shell/Shell_Int/Taskbar.htm)
"The taskbar supports two display options: Auto Hide and Always On Top. To set these options, the user must open the taskbar shortcut menu, click Properties, and select or clear the Auto Hide check box or the Always On Top check box. There is no way to set these options programmatically. To retrieve the state of these display options, use the ABM_GETSTATE message."
-- 摘抄自MSDN
这段话的意思就是通过编程是无法改变Taskbar的Auto Hide和Always On Top状态,只能够通过ABM_GETSTATE消息获取Taskbar的状态,我KAO!又是Microsoft的技术壁垒,M$自己肯定是要对Taskbar状态进行设置的,几乎肯定存在没有公开的API用来设置Taskbar状态,只是不明白Microsoft隐瞒这个无伤大雅的细节出于什么样的目的,不管那么多,我们试着获取Taskbar状态先,
function IsTaskbarAutoHideOn : boolean;
var
ABData : TAppBarData;
begin
ABData.cbSize := sizeof(ABData);
Result := (SHAppBarMessage(ABM_GETSTATE, ABData) and ABS_AUTOHIDE) > 0;
end;
function IsTaskbarAlwaysOnTop : boolean;
var
ABData : TAppBarData;
begin
ABData.cbSize := sizeof(ABData);
Result := (SHAppBarMessage(ABM_GETSTATE, ABData) and ABS_ALWAYSONTOP) > 0;
end;
上面的代码可以获取Taskbar的Auto Hide和Always On Top状态,需要使用SHAppBarMessage函数发送ABM_GetState消息,唯一一点值得注意的是,MSDN提到必须填充APPBARDATA 数据结构的cbSize和hWnd成员数据。但是我并没有填写hWnd数据成员,仍然得到了正确的结果。
以上函数可以参考以下网址:
http://msdn.microsoft.com/library/psdk/shellcc/shell/Messages/ABM_GETSTATE.htm
通过使用spy++(主要是为了获取Taskbar窗口句柄),我的研究开始变得非常有趣,Taskbar的类名为ShellTrayWnd(下图蓝色线框的部分),通过FindWindow函数获取Taskbar的窗口句柄,我们就可以通过ShowWindow函数进行窗口的隐藏和显示。
procedure ShowTaskbar(bShow : boolean);
var
HTaskbar : HWND;
begin
HTaskBar:=FindWindow('Shell_TrayWnd', nil);
if bShow then
ShowWindow(HTaskbar,SW_SHOW)
else
ShowWindow(HTaskbar,SW_HIDE);
end;
到此算是勉强回答了那个网友的问题。
二、继续我们的Taskbar的搞笑研究。
procedure EnableTaskbar(bEnable : boolean);
var
HTaskbar : HWND;
begin
HTaskBar:=FindWindow('Shell_TrayWnd', nil);
EnableWindow(HTaskBar, bEnable);
end;
这个函数和上面的函数惊人的相似,这个函数不是隐藏Taskbar,而是让Taskbar不听使唤,使用的是EnableWindow函数。
其实,细心研究上面的spy++窗口会发现,Taskbar窗口还有很多的子窗口,这是几个相当有意思的类名称 -- Button、TrayClockWClass、ToolbarWindow32、SysTabControl32,我试着找到这些类实例的窗口句柄并分别隐藏了它们,感兴趣的朋友可以使用下面的函数试一试:
procedure ShowTaskbarClock(bShow: boolean);
var
TrayWnd, TrayNWnd, ClockWnd : Hwnd;
begin
TrayWnd := FindWindow('Shell_TrayWnd', nil);
TrayNWnd := FindWindowEx(TrayWnd,0,'TrayNotifyWnd', nil);
ClockWnd := FindWindowEx(TrayNWnd,0,'TrayClockWClass', nil);
if bShow = true then
ShowWindow(ClockWnd,sw_show)
else
ShowWindow(ClockWnd,sw_hide)
end;
// 显示、隐藏任务栏的时间显示
procedure ShowTaskbarStartBtn(bShow : boolean);
var
HTaskBar, HButton : Hwnd;
begin
HTaskBar := FindWindow('Shell_TrayWnd', nil);
HButton := FindWindowEx(HTaskBar,0,'Button', nil);
if bShow then
ShowWindow(HButton,sw_show)
else
ShowWindow(HButton,sw_hide) ;
end;
// 显示、隐藏任务栏的开始按键(Start按键)
procedure ShowToolbarWindow32(bShow: boolean);
var
TrayWnd, TrayNWnd, ToolbarWnd : Hwnd;
begin
TrayWnd := FindWindow('Shell_TrayWnd', nil);
TrayNWnd := FindWindowEx(TrayWnd,0,'TrayNotifyWnd', nil);
ToolbarWnd := FindWindowEx(TrayNWnd,0,'ToolbarWindow32', nil);
if bShow then
ShowWindow(ToolbarWnd,sw_show)
else
ShowWindow(ToolbarWnd,sw_hide)
end;
// 显示、隐藏任务栏的Shell Notify图标,比如金山词霸的图标
procedure ShowTaskbarApplicationButton(bShow: boolean);
var
TrayWnd, ReBarWnd, MSTaskWnd, SysTabWnd : Hwnd;
begin
TrayWnd := FindWindow('Shell_TrayWnd', nil);
ReBarWnd := FindWindowEx(TrayWnd,0,'ReBarWindow32', nil);
MSTaskWnd := FindWindowEx(TrayNWnd,0,'MSTaskSwWClass', nil);
SysTabWnd := FindWindowEx(ToolbarWnd,0,'SysTabControl32', nil);
if bShow then
ShowWindow(SysTabWnd,sw_show)
else
ShowWindow(SysTabWnd, sw_hide)
end;
// 显示、隐藏任务栏的所有应用程序任务栏
我使用以上的代码写了一个小程序并悄悄地"应用"到我一个同事的电脑上,看着光秃秃的Taskbar,他整整安装了五套杀毒软件对硬盘一阵猛扫,哈哈。 ^_^
三、深入一点研究Taskbar
最近一次开始对Taskbar重新发生兴趣也是因为网友在OICQ上面向我提出一个问题 -- "如何列举Taskbar上面显示的所有应用程序任务窗口"。
我翻阅了很多资料,并没有看到直接的解决方案,于是我变通的使用了另外一种解决方案。我使用了EnumWindiws函数 -- 枚举系统所有窗口,当然也包括了Taskbar上面的任务栏窗口。对窗口风格熟悉的朋友一定知道,对于一个窗口,如果具有WS_EX_TOOLWINDOW属性的窗口,是没有Taskbar的任务栏窗口的,很多资料演示了运行期修改窗口为WS_EX_TOOLWINDOW来隐藏Taskbar的任务窗口。另外,被隐藏的窗口也是没有Taskbar的相应窗口的,但是,仅仅这两个条件并不足以分辨出来窗口究竟是普通应用程序窗口,还是Taskbar任务栏窗口。
很偶然的机会下,我看到了一个相似的范例,终于得知,使用GetWindowLong函数的GWL_HWNDPARENT参数返回特定窗口的父窗口时,Taskbar的任务窗口一定是调用失败的,调用GetLastError返回的错误代码是998,通过RaizeLastWin32Error函数,我们知道该Exception名为:"Invalid access to memory location",这是一个比较常见的Exception,对此我也没有什么好的解释,事实上通过这个条件确实可以分辨出普通应用程序窗口和Taskbar任务窗口。以下代码片断演示了如何枚举并分拣出来Taskbar任务窗口。
function GetText(Wnd : HWND) : string;
var
textlength : integer;
text : PChar;
begin
textlength:=SendMessage(Wnd,WM_GETTEXTLENGTH,0,0);
if textlength=0 then
Result := ''
else begin
getmem(text,textlength+1);
SendMessage(Wnd,WM_GETTEXT,textlength+1,Integer(text));
Result:=text;
freemem(text);
end;
end;
function EnumWindowsProc (Wnd: HWND; LParam: LPARAM): BOOL; stdcall;
begin
Result := True;
if (IsWindowVisible(Wnd)) and
(GetWindowLong(Wnd, GWL_HWNDPARENT) = 0) and
(GetWindowLong(Wnd, GWL_EXSTYLE) and WS_EX_TOOLWINDOW = 0) then
Form1.Listbox1.items.add('Handle:'+Inttostr(Wnd)+',Text:'+GetText(Wnd));
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Param : Longint;
begin
EnumWindows(@EnumWindowsProc , Param);
end;
参阅:http://msdn.microsoft.com/library/wcedoc/wcesdkr/_tools_exstyle_statement.htm
本文所有代码经过我的测试,如果在您有任何疑问,欢迎一起探讨。衷心希望本文可以骗取ChinaPub的"Money"用以购买书籍(我还差500多 L),另外还要说的是,Kingron个王八蛋,版权所有,不准Copy俺的独家文字。(开玩笑的 J)
DragonPC(dragonpc@21cn.com)
作者会员名:dragonpc