windowsdelphiwinapifiremonkeywindows-messages

Intercepting Window status, position etc. in Firemonkey


I have user-friendliness in mind, so I'd like to intercept the position of each form I'm opening, to store and retrieve each time the same form is opened again.

Because I plan to port this for MAC, I decided to use FireMonkey.

Unfortunately, there is no event which is fired when a form is moved on the screen. I tried to intercept the message which should be fired when moving, but because nothing is happening, I think that Firemonkey forms do not work as VCL forms.

I'm using this declaration:

protected
  procedure WndMethod(var Msg: TMessage); virtual;

and this method implementation

procedure TMYForm.WndMethod(var Msg : TMessage);
var
  Handled: Boolean;
begin
  // Assume we handle message
  Handled := True;
  case Msg.Msg of
    WM_MOVE : begin
                Edit_X.Text :='x - ' + MYForm.Left.ToString;
                Edit_Y.Text :='y - ' + MYForm.Top.ToString;
              end;
    else
       Handled := False;
  end;
  if Handled then
    // We handled message - record in message result
    Msg.Result := 0
  else
    // We didn't handle message
    // pass to DefWindowProc and record result
    Msg.Result := DefWindowProc(FHWnd, Msg.Msg,
      Msg.WParam, Msg.LParam);
end;

I try to move the form on the screen, but no event is intercepted. I placed a breakpoint in the beginning of the method implementation, but nothing happens until I close the form. In this case, a message is intercepted.

What am I doing wrong?


Solution

  • First, you are not overriding any pre-existing virtual method, you are creating your own virtual method that is not hooked up to anything. That is why it never gets called.

    Second, unlike VCL, FireMonkey does not dispatch window messages to a virtual WndProc method that you can override, or to any message handlers that you may define on your form's class. A handful of select window messages are processed internally only (in a private WndProc() function inside the FMX.Platform.Win unit), they are not exposed publicly. Any unprocessed messages (including WM_MOVE) are discarded.

    So, on Windows, the only way to intercept window messages in FireMonkey is to call the Win32 SetWindowSubclass() or SetWindowLongPtr(GWLP_WNDPROC) API to hook the form's underlying Win32 HWND directly (see Subclassing Controls on MSDN for more details). You can override the form's virtual CreateHandle() method to call the API. You can get the HWND by calling the RTL's Platform.Win.FormToHWND() function.

    See How detect the mouse back and forward buttons in a Delphi FMX Windows form? for an example of such subclassing.

    Needless to say, using the Win32 API will not port to MacOS, however. So, you will have to use whatever platform-specific API that MacOS exposes to hook windows. I don't know what such an API looks like. And there is no function like FormToHWND() in the FMX.Platform.Mac unit, but there is a WindowHandleToPlatform() function that you can pass the form's Handle to. On MacOS, WindowHandleToPlatform() returns a TMacWindowHandle object containing Wnd: NSWindow, View: NSView, and Handle: TOCLocal properties which you can use with native MacOS APIs.

    That being said, there is a better alternative - you do not need to hook the underlying platform window directly at all, you can use FireMonkey's Save State feature instead. In the form's OnSaveState event, save the form's current Left/Top values to the form's SaveState property as needed, and then in the form's OnCreate event, you can restore the values from the SaveState property, if they were previously saved.