【问题标题】:delphi splash form doesn't fire timer events if shown non-modally如果以非模态方式显示,delphi 初始表单不会触发计时器事件
【发布时间】:2010-11-10 22:40:34
【问题描述】:

我编写了一个闪入、显示一段时间然后淡出的启动表单。淡入淡出是用一个定时器来实现的,它也关闭表单。它工作正常。

我以模态方式显示表单,但我怀疑主表单直到启动表单关闭后才开始构建和显示。

然后我想,如果我以非模态方式显示表单并使用 fsStayOnTop(即 SplashForm.Show 而不是 SplashForm.ShowModal),那么一旦显示启动表单,主表单就可以在启动表单后面初始化,这意味着启动画面关闭时,应用程序已准备就绪。

但我发现计时器事件不再触发。 TApplication.OnIdle 事件也没有。什么给了?

【问题讨论】:

  • 不要fsStayOnTop;人们想在您的应用加载时使用其他应用。没有应用程序不是那么特别,它应该防止这种情况! (The Old New Thing 很好地解释了没有应用程序是特别的:blogs.msdn.com/b/oldnewthing/archive/2009/02/20/9435239.aspx
  • 我想这是确保启动画面不会被主窗体隐藏的一种生硬的方式,这会破坏目的。另一方面,我不希望主窗体放在其他正在运行的应用程序后面,那么如何将启动窗体保持在应用程序主窗口的前面?

标签: delphi timer splash-screen non-modal


【解决方案1】:

你说这是一个闪屏。它是否在程序启动期间显示,在您点击 DPR 中的Application.Run; 行之前?如果是这样,那么 TApplication 事件循环还没有开始,所以你不会得到任何 OnIdle 事件。

【讨论】:

  • 是的,它被称为 DPR 正文中的第一件事。如果启动表单以模态方式显示,则计时器和 TApplicationEvents 似乎可以触发。
  • @user89691:是的,我认为 ShowModal 要么有自己的事件循环,要么做一些事情来启动应用程序事件循环。无论哪种方式,如果没有运行事件循环,您将不会触发计时器事件,通常通过调用 Application.Run
  • 嗯。那么我是否由此得出结论,我无法在主表单之前以非模态方式显示表单?我想我的问题应该是:“如何在应用程序启动后尽早显示启动表单,并在启动启动表单显示时让初始化继续应用程序主表单显示”其他应用程序似乎设法做到了。
  • ShowModal() 在内部运行自己的消息循环,因为调用线程的消息循环被阻塞,直到 ShowModal() 退出。
  • @user89691:您可以在调用 Application.Run() 之前显示非模态表单。只需在调用 TForm.Show() 后调用 TForm.Update()。只是不要依赖任何依赖于消息处理的功能,例如计时器,因为消息循环尚未运行。
【解决方案2】:

Fade 不适用于标准计时器,因为应用程序循环在您调用 Application.Run(如 Mason 所说)之前不会起作用,并且计时器是基于消息的计时器 API 机制的包装器。

您不能使用基于线程的计时器,因为它需要 Synchronize 才能与 UI 一起工作,而 Synchronize 是基于消息的机制。

但是您可以浪费淡入/淡出所需的时间,因此您可以获得一个精美的应用程序启动,如果您正在寻找这个,我认为您并不担心浪费一点时间。我可以用(工作和测试的)代码示例更好地解释,所以这对你有用:

USplashForm.pas:

//...
interface
//...
type
  TSplashForm = class(TForm)
    //...
  public
    procedure FadeIn;
    procedure FadeOut;
    //...
  end;

//...
implementation
//...
procedure TSplashForm.FadeIn;
begin
  AlphaBlend := True;
  AlphaBlendValue := 0;
  Show;
  Update;
  repeat
    AlphaBlendValue := AlphaBlendValue + 5;
    Update;
    Sleep(20);
  until AlphaBlendValue >= 255;
end;

procedure TSplashForm.FadeOut;
begin
  repeat
    AlphaBlendValue := AlphaBlendValue - 5;
    Update;
    Sleep(20);
  until AlphaBlendValue <= 5;
  Hide;
end;
//...

YourProject.dpr

var
  Splash: TSplashForm;

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Splash := TSplashForm.Create(nil);
  try
    Splash.FadeIn;
    //any initialization code here!!!
    Application.CreateForm(TMainForm, MainForm);
    MainForm.Show;
    MainForm.Update;
    //more code
    Sleep(500);  //I used it to delay a bit, because I only create one form and I have not initialization code at all!
    Splash.FadeOut;
  finally
    Splash.Free;
  end;
  Application.Run;
end.

我的 5 美分,享受吧。

【讨论】:

  • 自 D5 以来,TThread.Synchronize() 方法不再是基于消息的。在 D6 中,它被重写为不再使用消息(好吧,不是用于实际的方法调用,无论如何 - 它确实发布了 WM_NULL 消息以“唤醒”主线程)。在 D6 及更高版本中,如果消息循环尚未开始运行,您的代码可以手动定期(例如在 MainForm 的启动代码期间)调用 CheckSynchronize() 以允许处理 Tthread.Synchronize() 请求。
  • @Remy:对于在同步方法中不再使用消息,我显然已经过时了。无论如何,如果您必须定期检查 CheckSynchronize,对于这种特殊情况,淡入淡出可能不会像某人想要的那样好(例如我)。使用我的方法对于@User89691 想要的仍然有效,我认为这是一个很好的视觉效果(这当然是有效的!)
【解决方案3】:

我是通过以下方式完成的:

  • 我从“自动创建表单”中删除了启动表单。
  • 在我拥有的主要表单的FormCreate 中:

    with TfSplash.Create(Self) do Show;
    
  • 在初始表单中,我有以下内容:

    procedure TfSplash.FormShow(Sender: TObject);
    begin
      Timer.Enabled:=True;
    end;
    
    
    procedure TfSplash.TimerTimer(Sender: TObject);
    begin
      Release; // like free, but waits for events termination
    end;
    

【讨论】:

  • 这种方法的缺点是 MainForm 的 OnCreate 事件直到 DFM 流发生后才会触发。如果表单上有很多控件,或者它们需要一段时间来初始化,那么启动表单将不会立即出现。
  • ...这正是我想要避免的。理想情况下 - 我希望启动画面或多或少地立即弹出,以便用户在流式传输主表单时有一点眼睛糖果可以咀嚼。我想一种方法是使用 2 个单独的应用程序来完成。 App1 只会显示启动屏幕,然后执行 App2 - 主应用程序。 App1 会在延迟后关闭。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-21
  • 1970-01-01
  • 1970-01-01
  • 2017-01-30
  • 2011-05-07
  • 1970-01-01
相关资源
最近更新 更多