Where should I put code to execute once after my Delphi app has finished initialising?
Asked Answered
C

7

8

I have functions I want to perform after my app has finished initialising and the main form has been created. I did have the code (call it ProcedureX) in the forms OnShow event, but I have just noticed that it is being called twice, because OnShow is firing twice. It fires when the main program DPR calls:

Application.CreateForm(TMainForm, MainForm) ;  

as I would expect. But after that, when I read stuff from an INI file that includes the forms on-screen position, I have a call:

MainForm.position := poScreenCenter ;

This, it would appear fires the OnShow event again.

Where can I put my call to ProcedureX, which must only be called once, and which needs the main form to be created before it can execute?

Catarina answered 23/9, 2010 at 22:32 Comment(0)
G
5

You can test and set a flag once you call the procedure for the first time. Like so:

type
  TForm1 = class(TForm)
    procedure FormShow(Sender: TObject);
  private
    FRunOnce: Boolean;
  public
    [...]

[...]

procedure TForm1.FormShow(Sender: TObject);
begin
  if not FRunOnce then begin
    FRunOnce := True;
    ProcedureX;
  end;
end;
Gunny answered 24/9, 2010 at 1:45 Comment(7)
I follow the convention "prefix fields with a F", so FRunOnce sounds more standard for me. Is not a rule, just a convention, like prefix class names with T. Just makes other's code easier to read.Chokedamp
@Chokedamp - When there's the 'F' I somehow feel like it should have a getter/setter. But then that's my problem I guess... Edited the answer, thanks for pointing out.Gunny
OK, I had thought of doing that, but it seemed like treating the symptom, not the cause. I guess I was looking for some other event (like "OnEverythingFinished") which was only fired once. What I'm hearing is that there is none! Thanks for your help. FRunOnce it is.Catarina
This is probably the best way to do it, however, I'd add fRunOnce:= false into TForm1.OnCreate..just to be sure it is false to start with.Fagin
@sergeant - Oh, don't worry about its being false, a constructor always zeroes the memory that the object is going to use, all fields will have initial values of nil, 0, false etc..Gunny
Ahh, but can you be sure this is true of all compilers past, present and future ? Initialising variables is a good habit to get into - I've seen too many random and hard to find bugs that were down to data not being initialised!Fagin
@sergeant - Well, it is the documented behavior.Gunny
F
10

If your code only needs to run once per form creation (or per application and the form is only created once per application run), put the code in the form's OnCreate handler. It is the natural place for it to go.

Nowadays (since D3 I think) the OnCreate fires at the end of the construction process in the AfterConstruction method. Only if you were to set OldCreateOrder to True (and it is False by default), might you get in trouble as that makes the OnCreate fire at the end of the Create constructor.

Florindaflorine answered 24/9, 2010 at 6:46 Comment(0)
S
7

The normal order of execution for a Form is :

  • AfterConstruction: when the form and it components are fully created with all their properties.
  • OnShow: whenever the Form is ready to show (and, yes, any change causing a CM_SHOWINGCHANGED can trigger an OnShow)
  • Activate: whenever the Form takes the Focus

So, depending on what you need in ProcedureX, AfterConstruction might be enough, and is executed only once; just override it and add ProcedureX after inherited. It'll be after OnCreate.

If it is not the case, you can post a custom message to your Form from AfterConstruction, it will be queued and will reach your custom handler after the other messages have been handled.

In both cases, you would not need a extra boolean Field.

Schumann answered 24/9, 2010 at 3:12 Comment(2)
What about using OnCreate, then?Catarina
It might be OK. But AfterConstruction allows to always execute ProcedureX after whatever might be in OnCreate...Schumann
K
6

@Sertac,

There's really no need for the FRUNOnce field; simply do OnShow=NIL as the first line of your FormShow method.

FYI, The "run once" idiom -- setting the event handler field to NIL in the first line of the event handler -- is also terribly useful for getting some code up-and-running once a form has been completely initialized. Put your code in a FormActivate method and, as the first line of the method, set OnActivate=NIL.

Kandrakandy answered 24/9, 2010 at 19:18 Comment(1)
that would only be possible if you've got nothing else to do in the event handler. If however you've got code in the handler that you want it to run whenever you f.i. unhide your form, you cannot nil the handler.Gunny
G
5

You can test and set a flag once you call the procedure for the first time. Like so:

type
  TForm1 = class(TForm)
    procedure FormShow(Sender: TObject);
  private
    FRunOnce: Boolean;
  public
    [...]

[...]

procedure TForm1.FormShow(Sender: TObject);
begin
  if not FRunOnce then begin
    FRunOnce := True;
    ProcedureX;
  end;
end;
Gunny answered 24/9, 2010 at 1:45 Comment(7)
I follow the convention "prefix fields with a F", so FRunOnce sounds more standard for me. Is not a rule, just a convention, like prefix class names with T. Just makes other's code easier to read.Chokedamp
@Chokedamp - When there's the 'F' I somehow feel like it should have a getter/setter. But then that's my problem I guess... Edited the answer, thanks for pointing out.Gunny
OK, I had thought of doing that, but it seemed like treating the symptom, not the cause. I guess I was looking for some other event (like "OnEverythingFinished") which was only fired once. What I'm hearing is that there is none! Thanks for your help. FRunOnce it is.Catarina
This is probably the best way to do it, however, I'd add fRunOnce:= false into TForm1.OnCreate..just to be sure it is false to start with.Fagin
@sergeant - Oh, don't worry about its being false, a constructor always zeroes the memory that the object is going to use, all fields will have initial values of nil, 0, false etc..Gunny
Ahh, but can you be sure this is true of all compilers past, present and future ? Initialising variables is a good habit to get into - I've seen too many random and hard to find bugs that were down to data not being initialised!Fagin
@sergeant - Well, it is the documented behavior.Gunny
S
5

You can add a procedure in your DPR file, after Application.CreateForm. Put all code you need to initialize in that procedure. Works best when you have multiple forms in your app.

Also if the initialization takes a lot, it let's the program to display the forms on the screen so the user will know that the app is loading.

Example:

PROGRAM MyProgram;
begin
    Application.Initialize;
    Application.CreateForm(TMyForm, MyForm);
    MyForm.Show;

    LateInitialize;        <----------- here

    Application.Run;
end. 
Statement answered 13/9, 2012 at 17:34 Comment(0)
F
1

I'm going to propose a bit different approach to this answer by Server Overflow. We will achieve almost exactly same effect, but without any edit inside the DPR file (main project source file). We will get there by using a class helper in the unit of our main form:

type
{ TAppHelper }
  TAppHelper
  = Class helper for TApplication
      Public Procedure Run;
    End;

  Procedure TAppHelper.Run;
    begin
      Unit1.MainForm.PreRun;
      inherited Run;
    end;

Notice, the Unit1.MainForm.PreRun is some method in your main form, with only one caveat: if your main form is called "MainForm", then you need to prefix it with your unit's name inside the helper's method, because the TApplication class already has a member called MainForm. Incidentally, if you do leave out the prefix, this might still work, given that your Unit1.MainForm is indeed application's main form as well.

The reason why this works, is because the Unit1 is on the uses list of the DPR project, and as long as the TAppHelper is defined in the interface section (not in the implementation section), it will get loaded and by the time the Application.Run method is called in the DPR file, this will already be the helper version of it.

The beauty of this is, that it will run exactly one time, and exactly after all the forms are already created, after all their constructors have already been executed. And the fact that we're effectively customizing the Application.Run call in the DPR file, without editting the DPR file, is kind of ingenious. Again, class helpers in delphi/lazarus !

I will share one more neat trick, first take a look:

  Procedure TAppHelper.Run;
    begin
      TTask.Run(
        procedure
          begin
            sleep(10);
            TThread.Synchronize(nil, procedure begin Unit1.MainForm.PreRun; end);
          end
        );
      inherited Run;
    end;

This is a trick I use whenever I want the code to execute with a small delay. Why? Because if your code runs before the inherited Run method, it might (depending what happens inside of that code) hang the UI momentarily, but just long enough for the form to flicker and appear not responsive during its startup. Also, we can't simply put the code behind the inherited Run method, because that won't get executed until the application gets terminated. So instead I use TTask from the System.Threading unit. The sleep(10) is probably an overkill, sleep(1) would most likely do the job, possibly even no sleep at all would work, but I do some complex initialization there, so I keep the delay generous. Bonus: if you don't update UI from your PreRun custom method, then you don't even need TThread.Synchronize wrapper, and it becomes even simpler. In case of FPC/Lazarus you can achieve the same by using TApplication.QueueAsyncCall() instead of TTask class.

I really think it's a neat trick, because I can code it entirely outside of the DPR file, in the unit of the form which defines the PreRun method, and it's guaranteed after ALL Forms are already created, not just the one where I implement my PreRun method. Also, if the class helper is in the unit of the form, instead elsewhere, then the PreRun doesn't even need to be public, it will work with protected or even private method as well! This is great for keeping this little logic away from any other part of the code.

Funambulist answered 4/8, 2022 at 17:9 Comment(0)
K
0

@Sertec,

Your code won't work either if you want it to run for every unhide event (you haven't put in any code to reset the frunonce field).

So your method would need to reset frunonce field, and mine would need to set OnShow=FormShow. Same difference, except that you need an additional field.

Kandrakandy answered 27/9, 2010 at 15:51 Comment(3)
If I'd be resetting the flag why would I have it? Example: I have to run procedure ShowJustOnce after the form becomes visible for the first time, on OnShow. And I've to run UpdateInfo every time a user causes to re-show the form, on OnShow. I cannot nil the handler because 'UpdateInfo' won't run. I have to use the flag because otherwise 'ShowJustOnce' would be running everytime the form is re-shown.Gunny
If you need some code in FormSHow that runs once and some that runs multiple times, then yes, you need a flag. That's not really relevant to the question that both of us were answering, which was merely "how do I have something only execute a single time when my form is initially shown". So, again...put it in the FormActivate method, and, as the first line of the method, put "OnActivate := NIL ;". If you want stuff to run every time the form is shown, and only once per actual visibility change, that's a different question.Kandrakandy
It is relevant and it is not a different question. I wouldn't suggest anyone to nil his event handler since I wouldn't know if there's code already there or not, and will not be in the future too... Like in on 'OnShow', one might have code in 'OnActivate' as well. I for instance, have code that re-shows forms which were previously hidden on 'OnDeactivate'. If I were to nil 'OnActivate', where would you suggest me to show them again?Gunny

© 2022 - 2024 — McMap. All rights reserved.