我的 Delphi 应用程序完成初始化后,我应该在哪里放置要执行的代码?

发布于 2024-09-24 09:23:53 字数 405 浏览 7 评论 0原文

在我的应用程序完成初始化并创建主窗体后,我想要执行一些功能。我确实在表单 OnShow 事件中有代码(称为 procedureX),但我刚刚注意到它被调用了两次,因为 OnShow 触发了两次。当主程序 DPR 调用时它会触发:

Application.CreateForm(TMainForm, MainForm) ;  

正如我所期望的那样。但在那之后,当我从包含表单在屏幕上位置的 INI 文件中读取内容时,我收到一个调用:

MainForm.position := poScreenCenter ;

这似乎会再次触发 OnShow 事件。

我可以在哪里放置对 procedureX 的调用,它只能调用一次,并且需要先创建主窗体才能执行?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

拥抱我好吗 2024-10-01 09:23:53

如果您的代码只需在每次创建表单时运行一次(或在每个应用程序中运行一次,且每次应用程序运行时只需创建一次表单),请将代码放入表单的 OnCreate 处理程序中。这是它自然要去的地方。

现在(我认为是从 D3 开始)OnCreate 在 AfterConstruction 方法的构造过程结束时触发。仅当您将 OldCreateOrder 设置为 True(默认情况下为 False)时,您可能会遇到麻烦,因为这会使 OnCreate 在 Create 构造函数的末尾触发。

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.

巷子口的你 2024-10-01 09:23:53

表单的正常执行顺序是:

  • AfterConstruction:当表单及其组件及其所有属性完全创建时。
  • OnShow:只要表单准备好显示(是的,任何导致 CM_SHOWINGCHANGED 的更改都可以触发 OnShow
  • Activate< /code>:每当 Form 获得焦点

时,根据您在 procedureX 中的需要,AfterConstruction 可能就足够了,并且只执行一次;只需覆盖它并在inherited之后添加ProcedureX即可。 它将在OnCreate之后进行。

如果不是这种情况,您可以从 AfterConstruction 将自定义消息发布到您的表单,它将排队并在处理其他消息后到达您的自定义处理程序。

在这两种情况下,您都不需要额外的布尔字段。

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.

故人爱我别走 2024-10-01 09:23:53

@Sertac,

确实不需要 FRUNOnce 字段;只需将 OnShow=NIL 作为 FormShow 方法的第一行即可。

仅供参考,“运行一次”习惯用法——在事件处理程序的第一行中将事件处理程序字段设置为 NIL——对于在表单完全初始化后启动并运行一些代码也非常有用。将代码放入 FormActivate 方法中,并在该方法的第一行设置 OnActivate=NIL。

@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.

梦巷 2024-10-01 09:23:53

第一次调用该过程后,您可以测试并设置标志。就像这样:

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;

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;
翻了热茶 2024-10-01 09:23:53

您可以在 DPR 文件中的 Application.CreateForm 之后添加一个过程。
将初始化所需的所有代码放入该过程中。
当您的应用程序中有多个表单时效果最佳。

此外,如果初始化需要很多时间,它会让程序在屏幕上显示表单,以便用户知道应用程序正在加载。

例子:

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

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

    Application.Run;
end. 

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. 
英雄似剑 2024-10-01 09:23:53

我将针对服务器溢出的答案提出一种不同的方法。我们将实现几乎完全相同的效果,但无需在 DPR 文件(主项目源文件)内进行任何编辑。我们将通过在主窗体的单元中使用类帮助器来实现这一点:

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

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

注意,Unit1.MainForm.PreRun是主窗体中的某种方法,只有一个警告:如果您的主窗体称为“MainForm”,那么您需要在助手的方法中使用您的单元名称作为前缀,因为 TApplication 类已经有一个名为 MainForm 的成员。顺便说一句,如果您确实省略了前缀,这可能仍然有效,因为您的 Unit1.MainForm 确实也是应用程序的主窗体。

之所以有效,是因为 Unit1 位于 DPR 项目的使用列表中,并且只要在接口部分(而不是实现部分)中定义了 TAppHelper,它就会被加载,并且在应用程序启动时.Run 方法在 DPR 文件中调用,这已经是它的帮助程序版本。

这样做的美妙之处在于,它只会运行一次,并且恰好在所有表单已经创建之后,在它们的所有构造函数都已经执行之后。事实上,我们在 DPR 文件中有效地自定义 Application.Run 调用,而无需编辑 DPR 文件,这是一种巧妙的做法。再说一遍,delphi/lazarus 中的类助手!

我将分享一个更巧妙的技巧,首先看一下:

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

每当我希望代码以较小的延迟执行时,我都会使用这个技巧。为什么?因为如果您的代码在继承的 Run 方法之前运行,它可能(取决于该代码内部发生的情况)暂时挂起 UI,但时间足以让窗体在启动期间闪烁并显得不响应。另外,我们不能简单地将代码放在继承的 Run 方法后面,因为在应用程序终止之前它不会被执行。因此,我使用 System.Threading 单元中的 TTask。 sleep(10) 可能有点矫枉过正, sleep(1) 很可能会完成这项工作,甚至可能根本不睡觉也能工作,但我在那里做了一些复杂的初始化,所以我保持了足够的延迟。奖励:如果您不从 PreRun 自定义方法更新 UI,那么您甚至不需要 TThread.Synchronize 包装器,并且它会变得更加简单。对于 FPC/Lazarus,您可以通过使用 TApplication.QueueAsyncCall() 而不是 TTask 类来实现相同的效果。

我真的认为这是一个巧妙的技巧,因为我可以完全在 DPR 文件之外,以定义 PreRun 方法的表单为单位对其进行编码,并且在所有表单都已创建之后保证,而不仅仅是我实现我的表单的表单预运行方法。另外,如果类助手位于表单单元中,而不是其他地方,那么 PreRun 甚至不需要是公共的,它也可以与受保护的甚至私有的方法一起使用!这对于使这个小逻辑远离代码的任何其他部分非常有用。

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.

软的没边 2024-10-01 09:23:53

@Sertec,

如果您希望它为每个取消隐藏事件运行,您的代码也将无法工作(您没有输入任何代码来重置 frunonce 字段)。

因此,您的方法需要重置 frunonce 字段,而我的方法需要设置 OnShow=FormShow。相同的区别,只是您需要一个附加字段。

@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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文