2

Possible Duplicate:
Splash Screen Programatically
Show a splash screen while a database connection (that might take a long time) runs

Which is the best place to initialize code such as loading INI file? I want first to show the form on screen so the user know that the app is loading and ONLY after that I want to call lengthy functions such as LoadIniFile or IsConnectedToInternet (the last one is REALLY slow).

The OnCreate is not good because the form is not yet ready and it will not show up on screen.

I do this I DPR but not working always:

program Test;
begin
  Application.Initialize;
  Application.Title := 'Test app';
  Application.CreateForm(TfrmTest, frmTest);
  frmTest.Show;               <---------------------- won't show
  LateInitialize;
  Application.Run;
end.

The form will not show until LateInitialize (4-5 seconds) is executed.


procedure LateInitialize;
begin
 CursorBussy;
 TRY
  // all this won't work also. the form won't show
  frmTest.Visible:= TRUE;
  Application.ProcessMessages; 
  frmTest.Show;
  Application.ProcessMessages;
  frmTest.BringToFront;
  frmTest.Update;
  Application.ProcessMessages;

  DoSomethingLengthy;     {4-5 seconds}
 FINALLY
  CursorNotBussy;
 END;
end;     <--------- Now the form shows.

And yes, frmTest it is my only form (the main form).

Community
  • 1
  • 1
Gabriel
  • 20,797
  • 27
  • 159
  • 293
  • 2
    Um, yes it does. It's [right here](http://stackoverflow.com/a/3359841/62576) - the one with the big green checkmark and the three upvotes. And yes, they're on the same subject. So is your question, which is why I marked it as a [duplicate](http://dictionary.reference.com/browse/duplicate). – Ken White Jul 26 '12 at 23:32
  • 4
    If they're exactly on the same subject, then they're exact duplicates of this question by your own admission. It doesn't matter whether they have accepted answers. "Duplicate" is a property of the question, completely independent of whatever answers there may be. However, *both* the questions Ken linked have accepted answers. If you have questions that have already been asked here, and they don't have suitable answers to your situation, then there are ways of drawing attention to those questions so people will answer them. Asking a duplicate question is just one of those ways. – Rob Kennedy Jul 27 '12 at 00:36
  • 1
    I am not sure about this, but I think that if you switch in your DPR to ShowModal instead of Show, and execute the remaining load in a separate thread, it would work like a charm. I remember using something like on my old Delphi projects, but that was some time ago... – Vinícius Gobbo A. de Oliveira Jul 27 '12 at 02:15
  • @Ken - Oh... sorry, it has indeed an accepted answer. – Gabriel Jul 27 '12 at 02:31

4 Answers4

3

After calling frmTest.Show, you can call frmTest.Update to let it render onscreen, before then calling LateInitialize. But until Application.Run is called, the main message loop will not be running, so the form will not be able to do anything else until then.

Another option is to use the form's OnShow event to post a custom window message back to the form via PostMessage(), then have the form call LateInitialize when it receives that message at a later time. That will allow the form to process painting messages normally until LateInitialize is called.

Anything that blocks the main thread for more than a few milliseconds/seconds really should be moved into a separate worker thread instead (especially things like IsConnectedToInternet). The main thread should be used for running the UI.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
2

An easy way to do this, is to send a message to yourself. I do this all the time

const
  MSG_AFTERCREATE = WM_APP + 4711;

...
procedure OnCreate(Sender: TObject);
procedure AfterCreate(var message: TMessage); message MSG_AFTERCREATE;
...


Implementation

procedure OnCreate(Sender: TObject);
begin
  PostMessage(Self.Handle, MSG_AFTERCREATE, 0, 0);
end;

procedure AfterCreate(var message: TMessage);
begin
  //Do initializing here... the form is done creating, and are actually visible now...
end;
Lars Bargmann
  • 142
  • 1
  • 7
  • I love it! I will try it. THANKS. Very elegant. I wonder how nobody came with and answer like this until now! – Gabriel Aug 09 '12 at 18:28
1

Variant 1: Use TTimer with a 1 second delay, run it from main form's OnShow In TTimer do the initialisation This will give time for most components to initialize and draw themselves Variant 1.1: use message method in function and call Win API PostMessage (but not SendMessage aka Perform) from OnShow. This is seemilar but more cheap and fast. However that message "do init now" sometimes may be received before some complex component on the form would fully draw itself.

Variant 2: use threads (OmniThreadsLib or even plain TThread) Launch it from MainForm OnCreate and let it prepare all data in background, then enable all needed buttons, menus, etc That is truly the best way if you have long and blocking functions, liek you described IsConnectedToInternet.

Variant 3: use SplashScreen before showing main form. That is good because users see that application not read yet. That is bad for that very reason - people start feeling your program is slow. Google Chrome was told to draw their main form as picture in 1st moments just to make look "we are already started" even the actual control would be ready a bit later.

Arioch 'The
  • 15,799
  • 35
  • 62
  • Variant 1: what if the CPU is and the main form didn't had the time to update itself but the time on TTimer expired (the second passed)? – Gabriel Aug 09 '12 at 18:25
  • Variant2: in the initialization I have to do lots of GUI (INI file) and global vars initializations. The thread will interact with the main form a lot. – Gabriel Aug 09 '12 at 18:26
  • 1) for what i know WM_TIMER system message is posted and would be sooner or later worked out. But You may simply test it - set timer t o10 secs and use SysInternals ProcessExplorer to suspend program for 20 secs, then look what happens. – Arioch 'The Aug 10 '12 at 07:42
  • 2) i can't see why INI files are GUI, frankly. /// Maybe you are to make updatePart1/UpdatePart2/... events, so that thread would load some part of those vars and send those, so that main form would re-read those vars already initialized. But that would require splitting your program into almost not cross-dependent semantical blocks. – Arioch 'The Aug 10 '12 at 07:45
0

A long time ago in another forum far far away, someone posted the following to document the life cycle of a form. I have found it useful, so am sharing it here.

Create       OnCreate
Show         OnShow
Paint        OnPaint
Activate     OnActivate
ReSize       OnResize
Paint        OnPaint

Close query  OnCloseQuery
Close        OnClose
Deactivate   OnDeactivate
Hide         OnHide
Destroy      OnDestroy

Try the OnActivate event.

Max Williams
  • 821
  • 1
  • 7
  • 13
  • 1
    But remember that `OnActivate` can fire many times over the course of a program. – Rob Kennedy Jul 27 '12 at 00:32
  • 1
    -1. OnActivate fires every time the form loses focus and then gains it again (for instance, the user clicks on another form in your app, or switches to a different application and then back to yours). Unless your initialization code can run several times, OnActivate is absolutely the wrong place to put it. – Ken White Jul 27 '12 at 01:19
  • 2
    @ Rob Kennedy & Ken White: You guys are the experts, so I will defer to your superior knowledge. However, I do something similar all the time (but more often in the OnShow event). I just add a flag variable to indicate whether the initialization code has been run. If the initialization has occurred, I skip the initialization code. He asked where to put the initialization code, not how to code it. – Max Williams Jul 27 '12 at 06:36
  • 1
    OnActivate can work once only by putting `OnActivate := nil;` in the OnActivate event. –  Jul 27 '12 at 07:28
  • Thanks Max and Blobby. A very elegant and superior solution indeed! – Gabriel Aug 09 '12 at 18:30