Search code examples
delphidelphi-7

How to redirect mouse events to another control?


I have a situation where I have a TImage and on top of it a TPanel covering it partially and they share the same parent:

------------------
|  Image1        |
|  ------------  |
|  |  Panel1  |  |
|  ------------  |
|                |
------------------

Panel1 is receiving mouse down/move/up events and processing it (so does Image1), but in some situation I would like to "redirect" the mouse down message to Image1 as if to simulate that Image1 was clicked rather than Panel1.

Here is what I did:

procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if (ssLeft in Shift) then
    Beep;
end;

procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; 
  X, Y: Integer);
begin
  //...
end;

procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  ShowMessage('boo!');
end;

procedure TForm1.Panel1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  P: TPoint;
begin
  if FRedirectToImage then begin
    ReleaseCapture; // do I need to send a WM_LBUTTONUP as well to the panel?        
    GetCursorPos(P);
    P := ScreenToClient(P);
    Image1.Perform(WM_LBUTTONDOWN, MK_LBUTTON, Longint(PointToSmallPoint(P)));
    Exit;
  end;

  // Normal handling
  if (ssLeft in Shift) then begin
    // ...
  end;
end;

It works as expected but I'm not sure It's the right way.
My question is, am I doing it right? is there a better or recommended way of doing it?


Update (1) : Handling WM_NCHITTEST as suggested is a valid answer and I thought about it also. even setting Panel1.Enabled to False will route the mouse messages to the underlying Image1 control.

But (!) consider this situation where I click the x location on the Panel and still need to route the message to Image1:

------------------
|  Image1        |
|          --------------
|          |  Panel1  x |
|          --------------
|                |
------------------

My method works, but WM_NCHITTEST is not applicable in the described scenario. I still didn't get an answer if my method is valid or not. (or maybe I should ask another question with the above scenario?)


Solution

  • In case when the control from which you want to redirect mouse events will not be in its whole client area inside the control to which those events should be redirected (as you've shown in your question update), then the WM_NCHITTEST message might be send to another control. Then the only one way remains to use IMHO, redirect all mouse messages.

    As @David mentioned in his comment, you can do this message redirection in a global way by writing an event handler for the OnMessage event for TApplication. Or use a TApplicationEvents object.

    In the following example, you can define the range of messages, that will be redirected as well as specify the list of source and target controls for that redirection. For redirecting is used the OnMessage event of the TApplication object, but since your target is in this case TGraphicControl descendant, you can't only change the recipient of the incoming message, but you have to eat this message and perform the message on the target control through the Perform method by yourself.

    Here is the code showing how to redirect all mouse messages from Panel1 to Image1. You can get the whole testing project from here if you want:

    unit Unit1;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls, ExtCtrls;
    
    type
      TMsgRange = record
        MsgFrom: UINT;
        MsgTo: UINT;
      end;
      TRedirect = record
        Source: HWND;
        Target: TControl;
      end;
    
    type
      TForm1 = class(TForm)
        Memo1: TMemo;
        Panel1: TPanel;
        Image1: TImage;
        procedure FormCreate(Sender: TObject);
        procedure Image1MouseDown(Sender: TObject; Button: TMouseButton;
          Shift: TShiftState; X, Y: Integer);
        procedure Panel1MouseDown(Sender: TObject; Button: TMouseButton;
          Shift: TShiftState; X, Y: Integer);
        procedure Image1MouseUp(Sender: TObject; Button: TMouseButton;
          Shift: TShiftState; X, Y: Integer);
        procedure Panel1MouseUp(Sender: TObject; Button: TMouseButton;
          Shift: TShiftState; X, Y: Integer);
      private
        FRedirectList: array of TRedirect;
        FRedirectEnabled: Boolean;
        FRedirectMsgRange: TMsgRange;
        procedure ApplicationMessage(var AMessage: TMsg; var Handled: Boolean);
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    procedure TForm1.ApplicationMessage(var AMessage: TMsg; var Handled: Boolean);
    var
      I: Integer;
    begin
      if FRedirectEnabled and (AMessage.message >= FRedirectMsgRange.MsgFrom) and
        (AMessage.message <= FRedirectMsgRange.MsgTo) then
      begin
        for I := 0 to High(FRedirectList) do
          if (AMessage.hwnd = FRedirectList[I].Source) and
            Assigned(FRedirectList[I].Target) then
          begin
            Handled := True;
            FRedirectList[I].Target.Perform(AMessage.message,
              AMessage.wParam, AMessage.lParam);
            Break;
          end;
      end;
    end;
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      FRedirectEnabled := True;
      FRedirectMsgRange.MsgFrom := WM_MOUSEFIRST;
      FRedirectMsgRange.MsgTo := WM_MOUSELAST;
      SetLength(FRedirectList, 1);
      FRedirectList[0].Source := Panel1.Handle;
      FRedirectList[0].Target := Image1;
      Application.OnMessage := ApplicationMessage;
    end;
    
    procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    begin
      Memo1.Lines.Add('Image1MouseDown')
    end;
    
    procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    begin
      Memo1.Lines.Add('Image1MouseUp')
    end;
    
    procedure TForm1.Panel1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    begin
      Memo1.Lines.Add('Panel1MouseDown')
    end;
    
    procedure TForm1.Panel1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    begin
      Memo1.Lines.Add('Panel1MouseUp')
    end;
    
    end.