关于任务栏菜单

字体大小: 中小 标准 ->行高大小: 标准
看了N个关于任务栏菜单的贴子,例如,有一个网友问如何做出和Winamp3.0的任务栏类似的菜单,也有的网友问如何拦截任务栏菜单的弹出消息,实际上,这个问题很简单的。下面是Kingron的研究结果!

我们都知道,在Delphi Application中,有一个隐含的Window,就是TApplication,这是一个隐藏的窗口,他在后面默默处理这一切关于Application相关的东西,我们甚至可以使用ShowWindow(Application.Handle,SW_SHOW)来看到这个窗口!这些虽然是题外话,但是,我们下面要做的却和这个窗口相关,因此你如果要继续的话,最好先补习一下子类化、TApplication、WndProc、Windows的消息机制等等相关的知识。我们知道对于一个Form,可以很容易拦截System Menu的消息,例如我们只要拦截WM_SYSCOMMAND就可以得到窗口系统菜单的消息,那么要拦截菜单的弹出消息,我们只要拦截WM_INITMENU即可!我们可以做一个简单的实验,New一个Application,然后在主窗体里面添加类似代码:

private

{ Private declarations }

procedure wmtest(var msg:TMessage);message wm_initmenu;

..........

procedure TForm1.wmtest(var msg: TMessage);

begin

Caption:='InitMenu';

end;

然后运行程序,点击Sys Menu,你就会看到效果!到这里,我们已经发现,只要拦截WM_INITMENU消息,就可以知道系统菜单的弹出了!现在问题是,不过我怎么写代码,可是任务栏菜单的弹出对于WM_INITMENU的拦截没有任何反应!难道我们那里错了吗?难道还有其它的东西隐藏在后面?问题的关键在于我们拦截的窗口不对!对于任务栏的菜单而言,这个菜单就是Application窗口的System Menu!因此我们的关键在于要拦截Application窗口的WM_INITMENU消息!至此,我们已经前进了一大步!知道了这一点,就比较好办了,我们知道,Delphi中有一个TApplicationEvents控件,他有一个属性,就是OnMessage,因此我们只要把Application的消息都经过OnMessage处理不就可以了?看起来似乎是一个完美的方法,然而,但你在OnMessage添加类似的代码的时候,你会发现,根本没有任何作用:

procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;

var Handled: Boolean);

begin

if Msg.message = wm_initmenu then

Caption:='OK';

end;

那个地方出现了错误?没有,哪里都没有,问题是,Delphi在封装的时候,过滤掉了一些东西,Application的OnMessage并不能处理程序的每一个消息,有些东西被他丢掉了!看来此路不通,必须另外找一个方法!然而幸运的是,我们都知道,在Windows的每一个窗口中,都有一个WndProc过程,这个过程负责处理“所有”的消息,因此我们只要Hook这个WndProc即可,也就是说我们只要把TApplication窗口的WndProc过程让我们接管,我们处理之后,然后仍然交给原来的WndProc处理即可,这样就可以达到我们的目的!那么如何来Hook这个Application窗口的WndProc过程呢?很简单,只要两个函数和几句简单的代码就可以了。必须用到的函数是GetWindowLong、SetWindowLong、CallWindowProc,首先我们用GetWindowLong获取原来的WndPro过程:

OldWndProc := TFarProc(GetWindowLong(Application.Handle, GWL_WNDPROC));

其中OldWndProc就是用来保存原来的WndProc过程的,因为我们还需要他!

然后利用SetWindowLong来挂接我们的WndProc过程:

SetWindowLong(Application.Handle, GWL_WNDPROC,longint(@NewWndProc));

此处的NewWndProc就是我们自己的WndProc处理过程!类似下面:

function NewWndProc(hWndAppl: HWnd; Msg, wParam: Word; lParam: Longint): Longint; stdcall;

begin

NewWndProc := 0; { Default WndProc return value }

{ * * * Handle messages here; The message number is in Msg * * * }

case msg of

WM_INITMENU:MessageBox(0,'OK','Info',MB_OK+MB_ICONINFORMATION);

end;

NewWndProc := CallWindowProc(OldWndProc, hWndAppl, Msg, wParam, lParam);

end;

至此,问题已经解决了,如果想简单一点儿的话,我们可以做一个简单的控件即可。

unit TaskMenu;

{*************************************************************}

{* *}

{* TaskMenu Control,Copyright Kingron 2002 *}

{* All rights reserverd. *}

{* Bug Report : Kingron@163.net *}

{* WEB : http://kingron.myetang.com *}

{* Special Thank:ChongChong(http://www.lkgarden.com/lfpsoft) *)

{* *}

{*************************************************************}

interface

uses

Windows, Messages, SysUtils, Classes, Menus, Forms, Controls;

type

TTaskMenu = class(TPopupMenu)

private

{ Private declarations }

protected

{ Protected declarations }

public

{ Public declarations }

constructor Create(AOwner: TComponent); override;

destructor Destroy; override;

published

{ Published declarations }

end;

procedure Register;

implementation

const

CM_POPUP_MENU = WM_USER + $500;

CM_APP_MENU = $0313;

var

OldWndProc : Pointer;

SysMenu : TPopupMenu;

procedure Register;

begin

RegisterComponents('Samples', [TTaskMenu]);

end;

{ TTaskMenu }

function NewWndProc(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): UINT; stdcall;

begin

Result := 0;

case Msg of

{ Not use SendMessage }

CM_APP_MENU: PostMessage(HWND, CM_POPUP_MENU, 0, 0);

CM_POPUP_MENU: SysMenu.Popup(Mouse.CursorPos.X, Mouse.CursorPos.Y);

else

Result := CallWindowProc(OldWndProc, hWnd, Msg, wParam, lParam);

end;

end;

constructor TTaskMenu.Create(AOwner: TComponent);

begin

inherited;

SysMenu := Self;

OldWndProc := Pointer(GetWindowLong(Application.Handle, GWL_WNDPROC));

if not (csDesigning in ComponentState) then

SetWindowLong(Application.Handle, GWL_WNDPROC, longint(@NewWndProc));

end;

destructor TTaskMenu.Destroy;

begin

SetWindowLong(Application.Handle, GWL_WNDPROC, longint(OldWndProc));

inherited;

end;

end.

大家注意,上面有一个全局变量,这个是不符合OO要求的,但是我们使用SetWindowLoing中的第三个参数,又不能是Object的方法,那么怎么办呢?可以利用指针来做:

在TTaskMenu中,设立一个Pointer:MyProc;

然后修改Create代码:

OldWndProc := Pointer(GetWindowLong(Application.Handle, GWL_WNDPROC));

MyProc:=Classes.MakeObjectInstance(Hooked);

SetWindowLong(Application.Handle, GWL_WNDPROC, longint(MyProc))

而Hooked就是一个Wndproc过程,这样,就可以实现OO的思想了。

最后,别忘记在需要的时候,使用FreeObjectInstance(MyProc)释放掉资源。

最后修改如下:

unit TaskMenu;

{*************************************************************}

{* *}

{* TaskMenu Control,Copyright Kingron 2002 *}

{* All rights reserverd. *}

{* Bug Report : Kingron@163.net *}

{* WEB : http://kingron.myetang.com *}

{* Special Thank:ChongChong(http://www.lkgarden.com/lfpsoft) *)

{* *}

{*************************************************************}

interface

uses

Windows, Messages, SysUtils, Classes, Menus, Forms, Controls;

type

TTaskMenu = class(TPopupMenu)

private

{ Private declarations }

OldWndProc: Pointer;

NewWndProc: Pointer;

protected

{ Protected declarations }

procedure HookWin;

procedure UnHookWin;

procedure Hooked(var Msg: TMessage);

public

{ Public declarations }

constructor Create(AOwner: TComponent); override;

destructor Destroy; override;

published

{ Published declarations }

end;

procedure Register;

implementation

const

CM_POPUP_MENU = WM_USER + $500;

CM_APP_MENU = $0313;

procedure Register;

begin

RegisterComponents('Samples', [TTaskMenu]);

end;

{ TTaskMenu }

procedure TTaskMenu.Hooked(var Msg: TMessage);

begin

case Msg.Msg of

CM_APP_MENU: PostMessage(Application.Handle, CM_POPUP_MENU, 0, 0);

CM_POPUP_MENU: Popup(Mouse.CursorPos.X, Mouse.CursorPos.Y);

else

Msg.Result := CallWindowProc(OldWndProc, Application.Handle, Msg.Msg, Msg.wParam, Msg.lParam);

end;

end;

constructor TTaskMenu.Create(AOwner: TComponent);

begin

inherited;

HookWin;

end;

destructor TTaskMenu.Destroy;

begin

UnHookWin;

inherited;

end;

procedure TTaskMenu.HookWin;

begin

OldWndProc := Pointer(GetWindowLong(Application.Handle, GWL_WNDPROC));

NewWndProc := Classes.MakeObjectInstance(Hooked);

if not (csDesigning in ComponentState) then

SetWindowLong(Application.Handle, GWL_WNDPROC, longint(NewWndProc));

end;

procedure TTaskMenu.UnHookWin;

begin

SetWindowLong(Application.Handle, GWL_WNDPROC, longint(OldWndProc));

if Assigned(NewWndProc) then Classes.FreeObjectInstance(NewWndProc);

NewWndProc := nil;

end;

end.

此文章由 http://www.ositren.com 收集整理 ,地址为: http://www.ositren.com/htmls/68124.html