Search code examples
androiddelphifiremonkey

When is a Delphi form actually visible to the user on Android?


The game that I'm developing does some pre-processing when first loading and I want to show the user a "loading %" to make it clear that the game is not frozen.

I am trying to do this by waiting for the main form to show for the user and then displaying an updating progress bar while performing the pre-processing.

For the pre-processing to work, I need two pieces of information:
1. The form is in it's final size (maximized across the entire display).
2. The form is visible to the user.

Here is a log showing the order of the form events being triggered as my game loads. Keep in mind that I'm not actually changing the form's size using code in any of these events:

FormCreate
FormResize, clientSize : 0x0
FormResize, clientSize : 0x0
FormResize, clientSize : 0x0
FormResize, clientSize : 0x0
FormShow
FormActivate
FormResize, clientSize : 360x640

Currently, I try to show the loading screen on the last resize (the one indicating the form is actually the size of the display), but at this point, the main form is still not visible (even after the FormShow and FormActivate) and I end up with the Android's default "gray gradient" screen showing until after my pre-processing code already finished, never showing the progress bar.

I tried calling "application.processmessages" after updating the progress bar, but it doesn't make a difference...

How do I detect when my main form is actually visible to the Android user?

[Update]
I created a small application to demonstrate this issue:
https://github.com/bLightZP/Test_Splash


Solution

  • This example illustrates using OnIdle, and also how it should be implemented.

    unit Unit1;
    
    interface
    
    uses
      System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
      FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls;
    
    type
      TForm1 = class(TForm)
        ProgressBar1: TProgressBar;
      private
        FShown: Boolean;
        procedure ApplicationOnIdleHandler(Sender: TObject; var Done: Boolean);
        procedure ExecutePreProcessing;
        procedure ExecutePreProcessingSynchronized;
      public
        constructor Create(AOwner: TComponent); override;
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.fmx}
    
    constructor TForm1.Create(AOwner: TComponent);
    begin
      inherited;
      // Method 1: Use OnIdle to execute code when the main form is visible:
      Application.OnIdle := ApplicationOnIdleHandler;
      // Method 2: Use a thread to execute the preprocessing. Comment out the line above if using this method, and uncomment the line below
      // TThread.CreateAnonymousThread(ExecutePreProcessingSynchronized).Start;
    end;
    
    procedure TForm1.ApplicationOnIdleHandler(Sender: TObject; var Done: Boolean);
    begin
      if not FShown then
      begin
        FShown := True;
        ExecutePreProcessing;
      end;
    end;
    
    procedure TForm1.ExecutePreProcessing;
    var
      I: Integer;
    begin
      for I := 1 to 100 do
      begin
        Sleep(50);
        ProgressBar1.Value := I;
        Application.ProcessMessages; // <---- Not the best idea
      end;
    end;
    
    procedure TForm1.ExecutePreProcessingSynchronized;
    var
      I: Integer;
    begin
      for I := 1 to 100 do
      begin
        Sleep(50);
        // Ensure UI updates happen in the main thread
        TThread.Synchronize(nil,
          procedure
          begin
            ProgressBar1.Value := I;
          end
        );
      end;
    end;
    
    end.