【问题标题】:Which is the best place to initialize code? [duplicate]哪个是初始化代码的最佳位置? [复制]
【发布时间】:2012-07-26 22:57:51
【问题描述】:

可能重复:
Splash Screen Programatically
Show a splash screen while a database connection (that might take a long time) runs

初始化代码(例如加载 INI 文件)的最佳位置是什么?我想首先在屏幕上显示表单,以便用户知道应用程序正在加载,然后才想调用冗长的函数,例如 LoadIniFile 或 IsConnectedToInternet(最后一个真的很慢)。

OnCreate 不好,因为表单还没有准备好,它不会显示在屏幕上。

我这样做我 DPR 但并不总是工作:

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

在执行 LateInitialize(4-5 秒)之前,表单不会显示。


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.

是的,frmTest 是我唯一的表单(主表单)。

【问题讨论】:

  • 那些帖子没有被接受的答案,而且它们完全在同一个主题上。
  • 嗯,是的。这是right here - 带有大绿色复选标记和三个赞成票的那个。是的,他们在同一个主题上。你的问题也是如此,这就是为什么我将其标记为duplicate
  • 如果他们完全在同一个主题上,那么他们就是你自己承认的这个问题的完全重复。他们是否接受答案并不重要。 “重复”是问题的一个属性,完全独立于可能存在的任何答案。但是,两个 Ken 链接的问题都已接受答案。如果您有已经在这里提出的问题,并且他们没有适合您的情况的答案,那么有一些方法可以引起人们对这些问题的注意,以便人们回答它们。提出重复的问题只是其中一种方式。
  • 我不确定这一点,但我认为如果您将 DPR 切换到 ShowModal 而不是 Show,并在单独的线程中执行剩余的负载,它会像一个魅力一样工作。我记得在我的旧 Delphi 项目中使用过类似的东西,但那是前一段时间......
  • @Ken - 哦...对不起,它确实有一个公认的答案。

标签: delphi


【解决方案1】:

在调用frmTest.Show之后,你可以调用frmTest.Update让它在屏幕上渲染,然后再调用LateInitialize。但在调用Application.Run 之前,主消息循环不会运行,因此在此之前表单将无法执行任何其他操作。

另一种选择是使用表单的OnShow 事件通过PostMessage() 将自定义窗口消息发布回表单,然后让表单在稍后收到该消息时调用LateInitialize。这将允许表单正常处理绘画消息,直到调用 LateInitialize

任何阻塞主线程超过几毫秒/秒的东西真的应该被移到一个单独的工作线程中(尤其是像IsConnectedToInternet这样的东西)。主线程应该用于运行 UI。

【讨论】:

    【解决方案2】:

    执行此操作的一种简单方法是向自己发送消息。 我一直这样做

    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;
    

    【讨论】:

    • 我喜欢它!我会试试。谢谢。十分优雅。我想知道直到现在没有人来回答这样的问题!
    【解决方案3】:

    变体 1:使用延迟 1 秒的 TTimer,从主窗体的 OnShow 运行它 在 TTimer 中进行初始化 这将为大多数组件提供时间来初始化和绘制自己 变体 1.1:在函数中使用消息方法并从 OnShow 调用 Win API PostMessage(但不是 SendMessage aka Perform)。这是看似但更便宜和更快的。然而,有时可能会在表单上的某些复杂组件完全绘制自身之前收到“do init now”消息。

    变体 2:使用线程(OmniThreadsLib 甚至普通的 TThread) 从 MainForm OnCreate 启动它,让它在后台准备所有数据,然后启用所有需要的按钮、菜单等 如果您有长且阻塞的功能,这确实是最好的方法,就像您描述的 IsConnectedToInternet。

    变体 3:在显示主窗体之前使用 SplashScreen。 这很好,因为用户看到该应用程序尚未读取。 正是因为这个原因,这很糟糕——人们开始觉得你的程序很慢。谷歌浏览器被告知在第一时间将它们的主要形式绘制为图片,只是为了看起来“我们已经开始了”,即使实际控制会稍后准备好。

    【讨论】:

    • 变体 1:如果 CPU 是并且主窗体没有时间更新自己但 TTimer 上的时间已过期(第二个已过)怎么办?
    • Variant2:在初始化中我必须做很多 GUI(INI 文件)和全局变量初始化。线程会与主窗体进行很多交互。
    • 1) 据我所知,WM_TIMER 系统消息已发布,迟早会解决。但是您可以简单地对其进行测试 - 将计时器设置为 o10 秒并使用 SysInternals ProcessExplorer 将程序挂起 20 秒,然后看看会发生什么。
    • 2) 坦率地说,我不明白为什么 INI 文件是 GUI。 /// 也许你要创建 updatePart1/UpdatePart2/... 事件,这样线程就会加载这些变量的一部分并发送它们,这样主窗体就会重新读取那些已经初始化的变量。但这需要将您的程序拆分为几乎不交叉依赖的语义块。
    【解决方案4】:

    很久以前在另一个很远的论坛上,有人发布了以下内容,以记录表单的生命周期。我发现它很有用,所以在这里分享。

    Create       OnCreate
    Show         OnShow
    Paint        OnPaint
    Activate     OnActivate
    ReSize       OnResize
    Paint        OnPaint
    
    Close query  OnCloseQuery
    Close        OnClose
    Deactivate   OnDeactivate
    Hide         OnHide
    Destroy      OnDestroy
    

    尝试 OnActivate 事件。

    【讨论】:

    • 但请记住,OnActivate 可以在程序过程中多次触发。
    • -1。 OnActivate 每次表单失去焦点然后再次获得焦点时触发(例如,用户单击您应用程序中的另一个表单,或切换到不同的应用程序然后返回您的应用程序)。除非你的初始化代码可以运行多次,否则 OnActivate 绝对是放错地方了。
    • @Rob Kennedy & Ken White:你们是专家,所以我会听从你们的高超知识。但是,我一直都在做类似的事情(但在 OnShow 事件中更常见)。我只是添加了一个标志变量来指示初始化代码是否已经运行。如果发生了初始化,我跳过初始化代码。他问初始化代码放在哪里,而不是如何编码。
    • OnActivate 只能通过将OnActivate := nil; 放入 OnActivate 事件中来工作一次。
    • 感谢 Max 和 Blobby。确实是一个非常优雅和卓越的解决方案!
    猜你喜欢
    • 2023-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-07
    • 1970-01-01
    • 1970-01-01
    • 2015-02-23
    相关资源
    最近更新 更多