-1

I have an application that restores windows on startup but this results in a potential flicker as each window is created and positioned.

To get around this I have the splash screen (stretched to the full size of the screen) set to "StayOnTop" and close it after the OnShow event using a TTask. The problem is that occasionally the splash screen gets stuck. If you click where buttons should be they redraw and show correctly. I have tried to "invalidate" all WinControls but this problem still shows up. I have never seen the problem in the debugger.

Are there any other tricks anyone can suggest to forcing a full repaint of the screen?

Here is my code to close the splash - This is in the OnShow of the main form.

aTask := TTask.Create(procedure()
  begin
    Sleep(800);
    TThread.Synchronize(nil, procedure()
      begin
        fSplash.Close;
        FreeAndNil(fSplash);
        DoInvalidate(self);
      end);
  end);
aTask.Start;

Here is my attempt to invalidate everything...

Procedure DoInvalidate( aWinControl: TWInControl );
var
  i: Integer;
  ctrl: TControl;
begin
  for i:= 0 to aWinControl.Controlcount-1 do
  begin
    ctrl:= aWinControl.Controls[i];
    if ctrl Is TWinControl then
      DoInvalidate( TWincontrol( ctrl ));
  end;
  aWinControl.Invalidate;
end;

Martin

Martin
  • 815
  • 8
  • 21
  • Did you try to use simple TTimer instead the TTask? Did you try to comment FreeAndNil(fSplash) and check if this changes anything? – zdzichs Jul 10 '17 at 09:21

1 Answers1

1

You don't need to recursively invalidate everything, just invalidating the Form itself is sufficient.

If you upgrade to 10.2 Tokyo, you can now use TThread.ForceQueue() instead of TThread.Synchronize() in a TTask:

procedure TMainForm.FormShow(Sender: TObject);
begin
  TThread.ForceQueue(nil, procedure
    begin
      FreeAndNil(fSplash);
      Application.MainForm.Invalidate;
    end
  );
end;

If you stick with TTask, you should at least use TThread.Queue() instead:

procedure TMainForm.FormShow(Sender: TObject);
begin
  TTask.Create(procedure
    begin
      TThread.Queue(nil, procedure
      begin
        FreeAndNil(fSplash);
        Application.MainForm.Invalidate;
      end;
    end
  ).Start;
end;

Or, you could just use a short TTimer, like zdzichs suggested:

procedure TMainForm.FormShow(Sender: TObject);
begin
  Timer1.Enabled := True;
end;

procedure TMainForm.Timer1Timer(Sender: TObject);
begin
  Timer1.Enabled := False;
  FreeAndNil(fSplash);
  Invalidate;
end;

Or, you could assign an OnClose event handler to the splash form to invalidate the MainForm, and then PostMessage() a WM_CLOSE message to the splash form:

procedure TMainForm.FormCreate(Sender: TObject);
begin
  fSplash := TSplashForm.Create(nil);
  fSplash.OnClose := SplashClosed;
  fSplash.Show;
end;

procedure TMainForm.FormShow(Sender: TObject);
begin
  if fSplash <> nil then
    PostMessage(fSplash.Handle, WM_CLOSE, 0, 0);
end;

procedure TMainForm.SplashClosed(Sender: TObject; var Action: TCloseAction);
begin
  fSplash := nil;
  Action := caFree;
  Invalidate;
end;

Or, use the OnDestroy event instead:

procedure TMainForm.FormCreate(Sender: TObject);
begin
  fSplash := TSplashForm.Create(nil);
  fSplash.OnDestroy := SplashDestroyed;
  fSplash.Show;
end;

procedure TMainForm.FormShow(Sender: TObject);
begin
  if fSplash <> nil then
    fSplash.Release; // <-- delayed free
end;

procedure TMainForm.SplashDestroyed(Sender: TObject);
begin
  fSplash := nil;
  Invalidate;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • ForceQueue is a new one to me. I will look that up but I have not made the move to Tokyo because of the bugs doing mobile development. I am not sure why Queue should be any different in this case but worth a try. – Martin Jul 10 '17 at 23:43
  • The Timer idea might be the best one although less elegant code wise. – Martin Jul 10 '17 at 23:43