Delphi:系统菜单是否打开?

时间:2010-10-10 06:14:13

标签: windows delphi winapi system

我是Delphi,我需要一个函数来确定是否打开了系统菜单(分别是窗口菜​​单,点击图标时出现的菜单)。原因是我正在编写一个反键盘记录功能,它将垃圾发送到当前活动的editcontrol(这也会阻止读取WinAPI消息的键盘记录器读取内容)。但是如果打开系统菜单,则editcontrol STILL具有焦点,因此垃圾将调用快捷方式。

如果我在TForm1中使用消息 WM_INITMENUPOPUP ,我可以确定系统菜单何时打开,但我希望我不必更改TForm,因为我想编写一个非可视组件,在TForm-derivate-class本身不需要任何修改。

//I do not want that solution since I have to modify TForm1 for that!
procedure TForm1.WMInitMenuPopup(var Message: TWMInitMenuPopup);  
begin  
 if message.MenuPopup=getsystemmenu(Handle, False) then  
 begin  
  SystemMenuIsOpened := true;  
 end;  
end;

TApplicaton.HookMainWindow()不会将WM_INITMENUPOPUP发送到我的钩子函数。

function TForm1.MessageHook(var Msg: TMessage): Boolean;  
begin  
Result := False;  
if (Msg.Msg = WM_INITMENUPOPUP) then  
begin  
// Msg.Msg IS NEVER WM_INITMENUPOPUP!  
 if LongBool(msg.LParamHi) then  
 begin  
  SystemMenuIsOpened := true;  
 end;  
end;  
end;  

procedure TForm1.FormCreate(Sender: TObject);  
begin  
 Application.HookMainWindow(MessageHook);  
end;  

procedure TForm1.FormDestroy(Sender: TObject);  
begin  
  Application.UnhookMainWindow(MessageHook);  
end;

即使经过长时间的研究,我也没有找到有关如何查询系统菜单是否打开的任何信息。我找不到任何方法来确定该菜​​单的开启+关闭。

请有人为我解决?

问候
丹尼尔马歇尔

3 个答案:

答案 0 :(得分:3)

Application.HookMainWindow没有做你想象的那样。它挂钩隐藏的应用程序窗口,而不是主窗体。要截取特定表单上的WM_INITMENUPOPUP,您需要做的只是为它编写一个处理程序,如您所见。

要为组件的任何所有者形式执行此操作,您可以指定表单的WindowProc属性来放置钩子:

unit FormHook;

interface

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

type
  TFormMessageEvent = procedure(var Message: TMessage; var Handled: Boolean) of object;

  TFormHook = class(TComponent)
  private
    FForm: TCustomForm;
    FFormWindowProc: TWndMethod;
    FOnFormMessage: TFormMessageEvent;
  protected
    procedure FormWindowProc(var Message: TMessage); virtual;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property OnFormMessage: TFormMessageEvent read FOnFormMessage write FOnFormMessage;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Test', [TFormHook]);
end;

procedure TFormHook.FormWindowProc(var Message: TMessage);
var
  Handled: Boolean;
begin
  if Assigned(FFormWindowProc) then
  begin
    Handled := False;

    if Assigned(FOnFormMessage) then
      FOnFormMessage(Message, Handled);

    if not Handled then
      FFormWindowProc(Message);
  end;
end;

constructor TFormHook.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FFormWindowProc := nil;
  FForm := nil;
  while Assigned(AOwner) do
  begin
    if AOwner is TCustomForm then
    begin
      FForm := TCustomForm(AOwner);
      FFormWindowProc := FForm.WindowProc;
      FForm.WindowProc := FormWindowProc;
      Break;
    end;
    AOwner := AOwner.Owner;
  end;
end;

destructor TFormHook.Destroy;
begin
  if Assigned(FForm) and Assigned(FFormWindowProc) then
  begin
    FForm.WindowProc := FFormWindowProc;
    FFormWindowProc := nil;
    FForm := nil;
  end;
  inherited Destroy;
end;

end.

然后,您可以在表单上使用此组件:

procedure TForm1.FormHook1FormMessage(var Message: TMessage; var Handled: Boolean);
begin
  case Message.Msg of
    WM_INITMENUPOPUP:
      ...
  end;
end;

问题可能是如果表单中有任何其他组件执行相同的操作,那么您需要确保以相反的顺序进行取消挂钩(最后挂钩,首先取消挂钩)。上面的例子在构造函数中挂钩并在析构函数中取消挂钩;即使在同一表单上有多个实例,这似乎也能正常工作。

答案 1 :(得分:2)

如果您不想对TForm-derivate-class进行任何修改,为什么不尝试使用纯Windows API方式来实现当前的解决方案,即使用SetWindowLongPtr()拦截WM_INITMENUPOPUP信息。用于拦截消息的Delphi VCL样式实际上只是这个Windows API函数的包装。

为此目的,使用SetWindowLongPtr()窗口过程设置新地址并获取窗口过程的原始地址。请记住将原始地址存储在LONG_PTR变量中。在32位Delphi中,LONG_PTRLongint;假设将来发布 64位 Delphi,LONG_PTR应为Int64;您可以使用$IFDEF指令将它们区分如下:

  Type
    {$IFDEF WIN32}
    PtrInt = Longint;
    {$ELSE}
    PtrInt = Int64;
    {$ENDIF}
    LONG_PTR = PtrInt;

用于此目的的nIndex参数值为GWLP_WNDPROC。此外,将窗口过程的新地址传递给dwNewLong参数,例如LONG_PTR(NewWndProc)NewWndProc是一个处理邮件的WindowProc Callback Function,它是您放置拦截标准并覆盖您要拦截的邮件的默认处理的地方。回调函数可以是任何名称,但参数必须遵循WindowProc约定。

请注意,您必须调用CallWindowProc()将未通过新窗口过程处理的任何消息传递给原始窗口过程。

最后,您应该再次在代码中的某处调用SetWindowLongPtr(),将已修改/新窗口过程处理程序的地址设置回原始地址。如上所述,原始地址已保存。

有一个Delphi code example here。它使用SetWindowLong(),但现在Microsoft建议使用SetWindowLongPtr()来使其与32位和64位版本的Windows兼容。

在Delphi 2009之前的Delphi SetWindowLongPtr()中不存在

Windows.pas。如果您使用旧版本的Delphi,则必须自己声明,或使用JwaWinUser单位JEDI API Library

答案 2 :(得分:0)

我自己没试过,但请试一试:

使用GetMenuItemRect获取GetSystemMenu返回的菜单第0项的矩形。 我(假设!)GetMenuItemRect如果系统菜单没有打开则应返回0(因为系统无法知道菜单项的矩形,除非它打开?)如果结果不为零,检查是否有坐标对于给定的屏幕分辨率,可以返回。

如果您有时间,可以查看AutoHotKey's source code以查看how to monitor when system menu is open/closed