Окно получает бесконечное количество сообщений, когда мышь подключена - PullRequest
3 голосов
/ 27 декабря 2011

Я пишу приложение, которое должно нарисовать круг там, где пользователь щелкает мышью. Чтобы добиться этого, я подключаю мышь по всему миру, используя SetWindowHookEx(WH_MOUSE,...)

Перехват и процедура, которая обрабатывает действия мыши, находятся в DLL. Процедура отправляет зарегистрированное сообщение, когда обнаруживает, что кнопка мыши была нажата с помощью PostMessage(FindWindow('TMyWindow',nil), MyMessage, 0,0);

Мое приложение с формой TMyWindow обрабатывает сообщения в процедуре WndProc. Я проверяю, совпадает ли пришедшее сообщение с моим зарегистрированным, и только затем рисую круг. После рисования круга я создаю таймер, который должен освободить изображение через 500 мс.

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

Я не понимаю, почему это так. Почему это работает нормально, когда я щелкаю где-то вне формы приложения, но зависает, когда я нажимаю внутри формы?

Дайте мне знать, если вам нужно больше деталей.

Спасибо

РЕДАКТИРОВАТЬ 1 :

Основной блок. Сообщение за 202 доллара - WM_LBUTTONUP.

unit main;

interface

uses
    HookCommon,
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls, Menus, AppEvnts;


type
    TTimer2 = class(TTimer)
    private
        FShape: TShape;
    public
        destructor Destroy; override;
        property Shape: TShape read FShape write FShape;
    end;

type
  TShowMouseClick = class(TForm)
    timerCountTimer: TTimer;
    tray: TTrayIcon;
    popMenu: TPopupMenu;
    mnuExit: TMenuItem;
    mnuActive: TMenuItem;
    N1: TMenuItem;
    mnuSettings: TMenuItem;
    timersStx: TStaticText;
    procedure timerCountTimerTimer(Sender: TObject);
    procedure mnuExitClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    timerList: TList;
    procedure shape();
    procedure freeInactive(var Msg: TMessage); message WM_USER + 1545;
  public
    shapeColor: Tcolor;                    
    procedure TimerExecute(Sender: TObject);
  protected
    procedure WndProc(var Message: TMessage); override;
    { Public declarations }
  end;

var
  ShowMouseClick: TShowMouseClick;



implementation
{$R *.dfm}

uses settings;

{$REGION 'Hide from TaskBar'}
procedure TShowMouseClick.FormActivate(Sender: TObject);
begin
  ShowWindow(Application.Handle, SW_HIDE);  
end;
procedure TShowMouseClick.FormShow(Sender: TObject);
begin
  ShowWindow(Application.Handle, SW_HIDE);
end;
{$ENDREGION}

procedure TShowMouseClick.WndProc(var Message: TMessage);
begin
    inherited WndProc(Message);
    if (Message.Msg = HookCommon.MouseHookMessage) and
        (Message.WParam = $202) then
        shape;
end;

procedure TShowMouseClick.FormCreate(Sender: TObject);
begin
  BorderStyle := bsNone;
  FormStyle := fsStayOnTop;
  WindowState := wsMaximized;

  mnuActive.Checked := true;
  HookCommon.HookMouse;
  timerList := TList.Create;
  timerList.Clear;
  shapeColor := clGreen;
end;

procedure TShowMouseClick.FormDestroy(Sender: TObject);
begin
    HookCommon.UnHookMouse;
end;

procedure TShowMouseClick.mnuExitClick(Sender: TObject);
begin
  Close;
end;

procedure TShowMouseClick.timerCountTimerTimer(Sender: TObject);
begin
  timersStx.Caption := 'Active timers: ' + IntToStr(timerList.Count);
end;

procedure TShowMouseClick.shape;  
var
  tm: TTimer2;
begin
  tm := TTimer2.Create(nil);

  tm.Tag := 0 ;
  tm.Interval := 1;
  tm.OnTimer := TimerExecute;
  tm.Shape := nil;
  timerList.Add(tm);
  timersStx.Caption := 'Active timers: ' + IntToStr(timerList.Count);
  tm.Enabled := true;
end;

procedure TShowMouseClick.TimerExecute(Sender: TObject);
var
    img: TShape;
    snd: TTimer2;
begin
    snd := nil;
    if Sender is TTimer2 then
        snd := TTimer2(Sender);

    if snd = nil then Exit;

    if snd.Tag = 0 then
    begin
        snd.Interval := 500;
        img := TShape.Create(nil);
        img.Parent := ShowMouseClick;
        img.Brush.Color := clGreen;
        img.Shape := stCircle;
        img.Width := 9;
        img.Height := 9;
        img.Left := Mouse.CursorPos.X-4;
        img.Top := Mouse.CursorPos.Y-3;
        snd.Tag := 1;
        snd.Shape := img;
    end else begin
        snd.Enabled := false;
        PostMessage(ShowMouseClick.Handle,WM_USER + 1545 , 0,0);
        Application.ProcessMessages;
    end;

end;

procedure TShowMouseClick.freeInactive(var Msg: TMessage);
var
    i: integer;
begin
    for i := timerList.Count - 1 downto 0 do
        if TTimer2(timerList[i]).Enabled = false then
        begin
            TTimer2(timerList[i]).Free;
            timerList.Delete(i);
        end;
end;

destructor TTimer2.Destroy;
begin
    FreeAndNil(FShape);
    inherited;
end;

end.

Общее устройство.

unit HookCommon;

interface

uses Windows;

var
  MouseHookMessage: Cardinal;

procedure HookMouse;
procedure UnHookMouse;

implementation

procedure HookMouse; external 'MouseHook.DLL';
procedure UnHookMouse; external 'MouseHook.DLL';

initialization
  MouseHookMessage := RegisterWindowMessage('MouseHookMessage');
end.

код DLL.

library MouseHook;

uses
  Forms,
  Windows,
  Messages,
  HookCommon in 'HookCommon.pas';

{$J+}
const
  Hook: HHook = 0;
{$J-}


{$R *.res}

function HookProc(nCode: Integer; MsgID: WParam; Data: LParam): LResult; stdcall;
var
  notifyTestForm : boolean;
begin

  notifyTestForm := false;

  if msgID = $202 then
    notifyTestForm := true;
  if notifyTestForm then
  begin
       PostMessage(FindWindow('TShowMouseClick', nil), MouseHookMessage, MsgID, 0);
  end;

  Result := CallNextHookEx(Hook,nCode,MsgID,Data);
end;

procedure HookMouse; stdcall;
begin
  if Hook = 0 then Hook:=SetWindowsHookEx(WH_MOUSE,@HookProc,HInstance,0);
end;

procedure UnHookMouse; stdcall;
begin
  UnhookWindowsHookEx(Hook);
  Hook:=0;
end;

exports
  HookMouse, UnHookMouse;

begin
end.

Источник хуков мыши this

Ответы [ 3 ]

3 голосов
/ 28 декабря 2011

Почему все работает нормально, когда я щелкаю где-то вне формы приложения, но зависает, когда я нажимаю внутри формы?

Вы не отправляете сообщение в другие окна, когда нажимаетена них.Сначала вы должны спросить себя: «Что произойдет, если я отправлю сообщение в своем обратном вызове хука всем окнам, которые публикуют WM_LBUTTONUP?».

Замените эту строку

PostMessage(FindWindow('TShowMouseClick', nil), MouseHookMessage, MsgID, 0);

вВаш код DLL, с этим:

PostMessage(PMouseHookStruct(Data).hwnd, MouseHookMessage, MsgID, 0);

Не имеет значения, будут ли другие приложения знать или нет, что такое MouseHookMessage, они будут игнорировать сообщение.Запустите ваше приложение и дико щелкните мышью на других окнах.Вообще ничего не случится.Если вы не нажмете в клиентской области любое приложение Delphi.Вы мгновенно заморозите его.

Ответ на этот вопрос заключается как в том, как работает цикл сообщений VCL, так и в том, как работает ловушка WH_MOUSE.Цитата из MouseProc документации функции обратного вызова .

[..] Система вызывает эту функцию всякий раз, когда приложение вызывает GetMessage или PeekMessage функция и есть сообщение мыши для обработки.

Предположим, что вы запускаете ваше приложение и мышь подключается, затем вы наводите мышь на форму и ждете, пока ваше приложение вызовет 'WaitMessage ', что он простаивает.Теперь нажмите в клиентской области, чтобы генерировать сообщения мыши.Что происходит, так это то, что ОС помещает сообщения в очередь сообщений основного потока вашего приложения.И что ваше приложение делает для удаления и отправки этих сообщений с PeekMessage.Это где приложения отличаются.Сначала VCL вызывает PeekMessage с параметром PM_NOREMOVE, переданным в параметре wRemoveMsg, в то время как большинство других приложений либо удаляют сообщение с вызовом PeekMessage, либо делают то же самое, используя GetMessage.

сейчасПредположим, настала очередь 'WM_LBUTTONUP.Обратитесь к цитате выше.Как только вызывается PeekMessage, ОС вызывает обратный вызов MouseProc.Вызов происходит из 'user32.dll', то есть, когда ваш обратный вызов ловушки вызывается, оператор, следующий за 'PeekMessage', еще не выполнен.Также запомните цикл VCL, сообщение все еще находится в очереди, оно не было удалено.Теперь ваша функция обратного вызова отправляет сообщение в ту же очередь сообщений и возвращает.Выполнение возвращается к циклу сообщений VCL, и VCL снова вызывает PeekMessage, на этот раз для удаления и отправки сообщения, но вместо удаления WM_LBUTTONUP удаляет отправленное вами пользовательское сообщение.WM_LBUTTONUP остается в очереди.После отправки пользовательского сообщения, поскольку «WM_LBUTTONUP» все еще находится в очереди, снова вызывается «PeekMessage», и снова ОС вызывает обратный вызов, чтобы обратный вызов мог опубликовать другое пользовательское сообщение, которое будет удалено вместо сообщения мыши.Этот цикл эффективно останавливает приложение.

Чтобы решить эту проблему, либо отправьте ваше сообщение в другой поток, который имеет свой собственный цикл сообщений, который каким-то образом синхронизируется с основным потоком, либо я бы не стал особенно советовать, но вместо публикации сообщения send Это.В качестве альтернативы вы можете самостоятельно удалить сообщение «WM_LBUTTONUP» из очереди, если оно существует:

procedure TShowMouseClick.WndProc(var Message: TMessage);
begin
    inherited WndProc(Message);
    if (Message.Msg = HookCommon.MouseHookMessage) and
        (Message.WParam = $202) then begin
      if PeekMessage(Msg, Handle, WM_LBUTTONUP, WM_LBUTTONUP, PM_REMOVE) then
        DispatchMessage(Msg);  // or eat if you don't need it.

     ..

end;

Недостатком этого подхода является то, что сам PeekMessage, как упоминалось выше, будет вызывать другое пользовательское сообщениебудут размещены, так что вы будете получать их в парах.

2 голосов
/ 28 декабря 2011

Либо щелчок мышью, либо сообщения MyMessage не удаляются из очереди сообщений (маловероятно), либо они каким-то образом возвращаются, либо ваш код повторяется в рекурсии.

Я бы попытался удалить любой код из вашего TMyWindow.WndProc и заменить его каким-нибудь безобидным кодом (например, OutputDebugString, чтобы увидеть, как он вызывается в области сообщений IDE), чтобы проверить, все еще он зацикливается или нет.
Что-то вроде:

  with Message do
    case Msg of
      WM_MyMessage: OutputDebugString('MyMessage received. Drawing a circle');
    else 
      inherited WndProc(Message);

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

Если это цикл, то ваш щелчок генерирует несколько сообщений или 1, который вращается вечно ...

Обновление:
Посмотрев на ваш код, я бы изменил способ работы с таймерами.
- Не создавайте таймер с интервалом 1 для создания фигуры. Вы будете наводнять ваше приложение событиями Таймера.
- Как только вы введете «Выполнить», отключите таймер
- Избегайте вызова Application.ProcessMessages.
- У вас могут быть некоторые причины, но я нахожу это очень запутанным, когда мне кажется, что простое событие OnMouse в вашей форме может легко достичь этого.

0 голосов
/ 27 декабря 2011

Это происходит потому, что FindWindow фактически отправляет сообщения самостоятельно, которые также оказываются в вашей ловушке.В частности, он отправляет WM_GETTEXT, чтобы получить заголовок окна.

Чтобы избежать этого, выполните FindWindow заранее (вне обратного вызова хука).

...