【问题标题】:How do I avoid to call Application.CreateForm twice?如何避免两次调用 Application.CreateForm?
【发布时间】:2011-01-11 15:03:18
【问题描述】:

我偶然发现了这个页面Why shouldn’t I call Application.CreateForm。 现在我有一些这样的代码:

SplashForm := TSplashForm.Create(Application);
SplashForm.Show;
SplashForm.Update; // force update
Application.Initialize;
Application.CreateForm(TClientData, ClientData);
SplashForm.Update; // force update
Application.CreateForm(TClientMainForm, ClientMainForm);
Application.ShowHint := True;

Application.Run;
ClientMainForm.ServerConnected := false;
FreeAndNil(ClientMainForm);
FreeAndNil(ClientData);

首先创建一个splashform,然后是一个数据模块,最后是主窗体。该页面说 Application.CreateForm 不应该被调用两次。上面的代码要不要改?

【问题讨论】:

    标签: delphi delphi-2007


    【解决方案1】:

    多次使用 Application.CreateForm 并没有错。但这会为每种形式引入全局变量,这可能是代码异味。 不幸的是,IDE 会为每个表单创建一个。尽管您可以根据需要删除它们。

    更好的方法是在需要时创建表单,并在准备好时释放它。所以你只使用 Application.CreateForm 作为主窗体。

    主数据模块可以由主窗体创建。但它也可以是全球性的,只是口味问题。

    所以要回答这个问题,您可以通过在本地创建和发布表单来避免 Application.CreateForm。

    文章提到了Application.CreateForm的副作用(第一个完成的表单是主表单)。 因此,如果主窗体使用 Application.CreateForm 创建其他窗体,可能会出现意想不到的副作用。

    因此,为了避免任何讨厌的事情,您应该将自己限制在一次调用中。仅使用一种全局形式即可完成。

    【讨论】:

    • 我认为删除 IDE 创建的全局变量不是更好的方式。这些是 Delphi 应用程序工作方式的一部分。与未优化的 IDE 生成的代码相比,这种“优化”实际上更像是一种“代码味道”。
    • @Warren:那条评论根本没有任何意义。除了主窗体变量之外,Delphi 应用程序不需要使用这些全局变量中的任何,并且主窗体的一个变量可以很容易地被项目文件中的变量替换,这任何其他单位都不知道,所以它也不是真正的全局变量。
    • 我只是在评论“虽然你可以根据需要删除它们”这句话,但我认为删除不是一个好主意。
    【解决方案2】:

    如果 TClientData 是一个数据模块,而 TClientMainForm 是一个表单,那么不需要(除了最后的两个 FreeAndNil 调用 - 不是真的需要)。但要小心。因为正如 Rob Kennedy 在他的帖子中所说,Application.CreateForm 在后面做了其他事情(它设置了 MainForm 变量),所以我建议根据以下规则设置您的项目文件:

    1. 使用对 Application.CreateForm 的单次调用创建您想要在启动时创建的所有表单 - 通常这由 IDE 完成。

    2. 从项目文件中删除要在程序中动态(按需)创建的表单。 (在项目 | 选项 | 表单...) - 将它们从“自动创建表单”移动到“可用表单”

    3. 你的代码中使用TmyForm.Create(Owner)(等等)和not创建你的表单Application.CreateForm(...)。顺便说一句,如果您确定要释放表单,那么最好(为了加快处理速度)调用 TmyForm.Create(nil) - iow 没有任何所有者。

    4. 如果您想在启动时进行某种初始化,您可以在项目文件中创建一个过程/方法,该过程/方法与已创建的表单/数据模块相关联,并在应用程序运行之前运行它.

    例如:

    begin 
      Application.Initialize; 
      Application.MainFormOnTaskbar := True; 
      Application.CreateForm(TdmoMain, dmoMain); //<--this is a data module
      Application.CreateForm(TfrmMain, frmMain); //<--this will became the main form
      Application.CreateForm(TfrmAbout, frmAbout);
      //... other forms created here...
      frmMain.InitEngine; //<--initialization code. You can put somewhere else, according with your app architecture
      Application.Run;
    end.
    

    通过这种方式,您将拥有干净的项目文件,并且您将确切地知道哪个是哪个。

    HTH

    【讨论】:

    • +1 PLAINTH 的回答解释并遵循大多数 Delphi 开发人员使用的既定约定。除此之外的任何事情都违反了我所说的“最小惊奇原则”。
    【解决方案3】:

    当我写那篇文章时,我主要考虑的是 DPR 文件外部的代码。人们在 DPR 文件中看到由 IDE 生成的表单创建代码,并认为这是通常创建表单的最佳方式,因此他们在程序的其他地方使用它。他们有时在主窗体的 OnCreate 事件处理程序中使用它来创建程序需要的其他窗体,然后他们遇到了问题,因为程序的主窗体不是他们认为的那样。

    在您提供的代码中,只需调用一次 CreateForm 很容易。将其用于主要形式,仅此而已。数据模块不是主窗体,所以不需要 CreateForm 的魔法。

    SplashForm := TSplashForm.Create(Application);
    SplashForm.Show;
    SplashForm.Update; // force update
    Application.Initialize;
    
    // Change to this.
    ClientData := TClientData.Create(Application);
    
    SplashForm.Update; // force update
    Application.CreateForm(TClientMainForm, ClientMainForm);
    Application.ShowHint := True;
    
    Application.Run;
    ClientMainForm.ServerConnected := false;
    
    // Remove these.
    FreeAndNil(ClientMainForm);
    FreeAndNil(ClientData);
    

    你真的不应该释放你在这里创建的对象,因为你不拥有它们。它们归全局 Application 对象所有,所以让它负责释放它们:删除对 FreeAndNil 的两个调用。

    【讨论】:

    • 如果你知道主窗体已经创建,那么另一个窗体不能神奇地变成主窗体。如果理解该警告,那么从 .dpr 源文件以外的其他位置调用 Application.CreateForm 是一个好主意,当且仅当您打算让 Application 对象从那时起管理表单的生命周期。但是,由于我们中的许多人都希望表单与应用程序一样存在,然后被释放,这正是调用 Application.CreateForm 所期望的行为。我称之为“后期表单创建”,我一直在使用它。
    • 主窗体在被指定为主窗体之前可以运行自己的代码。这就是问题发生的地方。如果您希望 Application 对象管理生命周期,那么只需在调用表单的构造函数时将其指定为 Owner 参数,就像您在创建任何其他对象时所做的那样,正如我在答案中的代码中所展示的那样。为什么要特别重视创建表单,而实际上它与创建任何其他类的实例相同? 调用 CreateForm 的唯一原因是当您需要 Application.MainForm 进行设置时。
    【解决方案4】:

    您引用的文章不正确。您需要多次调用 Application.CreateForm 的正当理由有很多

    1) 数据模块:您可能希望这些模块始终可用。最好的方法是 Application.CreateForm。我知道具有多个主题数据模块的应用程序,例如客户、发票、地址来处理数据库的不同区域并巧妙地封装功能。所有这些都是在 .dpr 中创建的

    2) 大而缓慢的加载内容(这本身就是一个坏主意,但这些事情会发生并且经常被支持程序员继承......)。将加载时间移动到应用程序启动中,就像您的示例代码以及启动屏幕更新一样。用户预计应用程序需要一段时间才能启动,这要归功于我们在 Microsoft Office 上工作的同事为降低对我们其他人的期望值所做的努力:)

    因此,总而言之,不要担心您的代码没有问题 - 但您可能会丢失“FreeAndNil”的内容。然而,小型快速点击对话框类型的东西最好通过以下方式调用:

    with TMyform.Create(nil) do
    try
      //Setup
      case ShowModal of
        // Whatever return values you care about (if any)
      end;
    finally
      Free;
    end;
    

    简短、甜美、切中要害并最大限度地减少内存使用...

    【讨论】:

    • 您关于链接文章不正确的断言实际上是非常不正确的。您给出的多个原因都不是致电Application.CreateForm() 的理由。 Re 1) 你可以简单地Create() 他们,传递任何Owner,包括Application。 Re 2) 你可以在主窗体的OnCreate 事件中做第一件事,没有人会看到任何区别。
    • 您可以以不同的方式创建表单,但您描述的方式涉及您必须编写额外的代码而没有明显的收益。为什么要故意以“非德尔福”的方式做事?如果您在 IDE 中创建表单或数据模块,Delphi 会帮助您在运行时使用它放入 .dpr 的 Application.Createform 语句来创建它。在与 Delphi 合作的 16 年中,我还没有看到任何证据表明 Application.Createform 对于应用程序生命周期所需的表单本质上是低效或不可取的,这似乎是作者的断言。
    猜你喜欢
    • 1970-01-01
    • 2021-12-14
    • 1970-01-01
    • 2021-05-16
    • 1970-01-01
    • 1970-01-01
    • 2014-06-20
    • 1970-01-01
    • 2021-10-16
    相关资源
    最近更新 更多