我是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;
即使经过长时间的研究,我也没有找到有关如何查询系统菜单是否打开的任何信息。我找不到任何方法来确定该菜单的开启+关闭。
请有人为我解决?
问候
丹尼尔马歇尔
答案 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_PTR
为Longint
;假设将来发布 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兼容。
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。