我的Delphi应用程序初始化完成后,我应该把代码放在哪里才能执行一次?

8

我有一些功能希望在我的应用程序完成初始化和主窗体被创建后执行。我曾经将代码(称其为ProcedureX)放在窗体的OnShow事件中,但我刚刚注意到它被调用了两次,因为OnShow事件会被调用两次。它会在主程序DPR调用时触发:

Application.CreateForm(TMainForm, MainForm) ;  

正如我所预期的那样。但之后,当我从INI文件中读取包含屏幕位置的表单时,我会调用:

MainForm.position := poScreenCenter ;

看起来,这会再次触发OnShow事件。

我应该把调用ProcedureX的代码放在哪里?ProcedureX只能被调用一次,并且需要主窗体创建后才能执行。

7个回答

10

如果您的代码仅需要在表单创建时运行一次(或者每个应用程序只创建一次表单),请将代码放置在表单的OnCreate事件处理程序中,这是自然的放置位置。

现在(从D3开始,我想),OnCreate事件会在AfterConstruction方法结束时触发。只有当您将OldCreateOrder设置为True(默认情况下为False)时,才可能会遇到问题,因为它会使OnCreate在Create构造函数的结尾处触发。


7
一个 Form 的正常执行顺序如下:
- `AfterConstruction`:当表单及其组件完全创建并具有所有属性时。 - `OnShow`:每当表单准备好显示时(是的,任何导致 `CM_SHOWINGCHANGED` 触发 `OnShow` 的更改都可以)。 - `Activate`:每当表单获得焦点时。
因此,根据 ProcedureX 中的需要,`AfterConstruction` 可能已经足够了,并且仅会被执行一次;只需重写它并在 `inherited` 后添加 ProcedureX 即可。这将在 `OnCreate` 之后执行。
如果不是这种情况,您可以从 `AfterConstruction` 向您的表单发布自定义消息,它将被排队并在其他消息处理完毕后到达您的自定义处理程序。
在这两种情况下,您都不需要额外的布尔字段。

那么使用OnCreate呢? - rossmcm
可能没问题。但是AfterConstruction允许在OnCreate中执行任何可能存在的ProcedureX之后始终执行。 - Francesca

6

@Sertac,

不需要使用FRUNOnce字段;只需将OnShow设置为NIL作为FormShow方法的第一行即可。

顺便提一下,“运行一次”惯用法--在事件处理程序的第一行将事件处理程序字段设置为NIL--对于在表单完全初始化后让一些代码运行非常有用。将您的代码放在FormActivate方法中,并将OnActivate设置为NIL作为该方法的第一行。


5
只有当事件处理程序中没有其他事情可做时,才能这样做。但是,如果你在处理程序中有代码需要在你取消隐藏表单等操作时运行,那么就不能将处理程序设为空。 - Sertac Akyuz

5

当您第一次调用该过程时,可以测试并设置标志。如下所示:

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;

我遵循“前缀字段为F”的约定,所以对我来说,FRunOnce听起来更标准。这不是规则,只是一种约定,就像使用T作为类名的前缀一样。这只是为了让其他人的代码更易于阅读。 - jachguate
这可能是最好的方法,但我会在TForm1.OnCreate中添加fRunOnce:= false..只是为了确保它一开始就是false。 - sergeantKK
1
@sergeant - 哦,不用担心它是假的,构造函数总是将对象要使用的内存清零,所有字段都将具有初始值nil、0、false等。 - Sertac Akyuz
啊,但你能确定这对所有过去、现在和未来的编译器都是正确的吗?初始化变量是一个好习惯——我见过太多由于数据未被初始化而导致的随机和难以找到的错误! - sergeantKK
2
@sergeant - 嗯,这是文档记录的行为 - Sertac Akyuz
显示剩余2条评论

5

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

此外,如果初始化需要很长时间,它会使程序显示在屏幕上,这样用户就会知道应用程序正在加载中。

示例:

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

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

    Application.Run;
end. 

1
我会提出一种与Server Overflow的这个答案略有不同的方法。我们将实现几乎完全相同的效果,但不需要在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项目的uses列表中,并且只要TAppHelper在接口部分(而不是实现部分)中定义,它就会被加载,当在DPR文件中调用Application.Run方法时,这将已经是其帮助器版本。
这样做的美妙之处在于,它只会运行一次,并且在所有窗体都已经创建并且它们的构造函数已经执行后运行。事实上,我们有效地定制了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类来实现相同的效果。
我真的认为这是一个很棒的技巧,因为我可以完全在定义PreRun方法的窗体单元之外编写代码,并且保证在所有窗体都已创建之后执行,而不仅仅是实现PreRun方法的那个窗体。而且,如果类助手在窗体单元中而不是其他地方,那么PreRun甚至不需要是公共的,它也可以使用受保护的或甚至是私有的方法!这对于将这个小逻辑与代码的任何其他部分分开非常有用。

0

@Sertec,

如果你想让你的代码在每次取消隐藏事件中运行(你没有放置任何代码来重置frunonce字段),那么你的代码也不会起作用。

因此,你的方法需要重置frunonce字段,而我的方法需要设置OnShow=FormShow。除了你需要一个额外的字段之外,两种方法都是一样的。


1
如果我要重置标志,为什么还要使用它呢?例如:我必须在表单第一次可见时,在OnShow上运行ShowJustOnce过程。每当用户导致重新显示表单时,我都必须运行UpdateInfo。我不能将处理程序设置为nil,因为'UpdateInfo'不会运行。我必须使用标志,否则'ShowJustOnce'将在每次重新显示表单时运行。 - Sertac Akyuz
如果您需要在FormShow中运行一次的代码和多次运行的代码,则确实需要一个标志。这与我们两个回答的问题并不相关,问题仅仅是“当我的表单最初显示时,如何使某些内容仅执行一次”。因此...将其放入FormActivate方法中,并将其作为该方法的第一行放置“OnActivate:= NIL;”。如果您希望每次显示表单时都运行一些内容,并且每次实际可见性更改时仅运行一次,则这是一个不同的问题。 - Erik Knowles
2
这是相关的,而且不是一个不同的问题。我不建议任何人将他的事件处理程序设置为nil,因为我不知道是否已经有代码存在,也不会在未来出现...就像在'OnShow'中,可能也有'OnActivate'中的代码。例如,我有重新显示以前在'OnDeactivate'上隐藏的表单的代码。如果我要将'OnActivate'设置为nil,您会建议我在哪里再次显示它们? - Sertac Akyuz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接