Delphi: Открыто ли системное меню? - PullRequest
2 голосов
/ 10 октября 2010

I Delphi, мне нужна функция, которая определяет, открывается ли системное меню (соответственно, меню окна, меню, которое появляется при нажатии значка). Причина в том, что я пишу функцию анти-кейлоггера, которая отправляет мусор в текущий активный editcontrol (это также предотвращает кейлоггер, который читает сообщения WinAPI для чтения содержимого). Но если открывается системное меню, фокус редактирования находится в режиме STILL, поэтому мусор будет вызывать ярлыки.

Если я использую сообщение WM_INITMENUPOPUP в моей TForm1, я могу определить, когда открывается системное меню, но я бы хотел, чтобы мне не приходилось менять TForm, поскольку я хочу написать невизуальный компонент, который не нуждается в каких-либо модификациях в самом TForm-производном-классе.

//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 ]

3 голосов
/ 10 октября 2010

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;

Проблема может заключаться в том, что если в форме есть какие-либо другие компоненты, которые делают то же самое, вам необходимо убедиться, что отцепление происходит в обратном порядке (последний подключен, первый отключен). Приведенный выше пример перехватывает в конструкторе и отцепляет в деструкторе; похоже, это работает даже с несколькими экземплярами в одной форме.

2 голосов
/ 10 октября 2010

Если вы не хотите вносить какие-либо изменения в TForm-производный класс, почему бы не попробовать чистый способ Windows API для реализации вашего текущего решения, то есть использовать SetWindowLongPtr () для перехвата WM_INITMENUPOPUP сообщение.Стиль Delphi VCL для перехвата сообщений на самом деле является просто оболочкой этой функции Windows API.

Для этой цели используйте SetWindowLongPtr(), чтобы установить новый адрес для оконной процедуры и получитьоригинальный адрес оконной процедуры, как одним ударом.Не забудьте сохранить исходный адрес в переменной LONG_PTR.В 32-битном Delphi LONG_PTR был Longint;предположим, что 64-bit 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 , которая обрабатывает сообщения, именно здесь вы устанавливаете критерии перехвата и переопределяете обработку сообщения, которое вы собираетесь перехватить, по умолчанию.Функция обратного вызова может быть любым именем, но параметры должны соответствовать соглашению WindowProc .

Обратите внимание, что необходимо вызвать CallWindowProc () , чтобы передать любые сообщения, не обработанныеновая оконная процедура к исходной оконной процедуре.

Наконец, вам нужно снова вызвать SetWindowLongPtr() где-нибудь в вашем коде, чтобы установить адрес обработчика модифицированной / новой оконной процедуры обратно в исходныйадрес.Первоначальный адрес был сохранен ранее, как указано выше.

Здесь был пример кода Delphi .Он использовал SetWindowLong(), но теперь Microsoft рекомендует использовать SetWindowLongPtr() вместо этого, чтобы сделать его совместимым как с 32-битной, так и с 64-битной версиями Windows.

SetWindowLongPtr() не существовало в Windows.pasDelphi до Delphi 2009. Если вы используете более старую версию Delphi, вы должны объявить ее самостоятельно или использовать JwaWinUser единицу JEDI API Library .

0 голосов
/ 10 октября 2010

Сам не пробовал, но попробую:

Используйте GetMenuItemRect, чтобы получить прямоугольник для пункта 0 меню, возвращаемого GetSystemMenu.Я (предположим!) GetMenuItemRect должен вернуть 0, если системное меню не открыто (потому что система не может знать прямоугольник пункта меню, если он не открыт?) Если результат не равен нулю, проверьте, возвращены ли координатывозможно для данного разрешения экрана.

Если у вас есть время, вы можете заглянуть в исходный код AutoHotKey , чтобы увидеть как отслеживать, когда системное меню открыто / закрыто .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...