Delphi TForm的OnShown事件?

Delphi TForm的OnShown事件?,delphi,delphi-xe2,tform,Delphi,Delphi Xe2,Tform,在程序启动时,在OnActivate事件处理程序中,我需要执行一些操作,将程序阻塞几秒钟。在此期间,表单的客户端区域仍然没有完全绘制,这对用户来说很难看。在这段阻塞时间内,我不需要程序响应单击或其他用户操作,因此不需要将阻塞操作放入线程中-我只需要完全绘制表单。因此,我使用TForm.Update和Application ProcessMessages在阻塞操作之前更新表单,效果非常好: procedure TForm1.FormActivate(Sender: TObject); begin

在程序启动时,在OnActivate事件处理程序中,我需要执行一些操作,将程序阻塞几秒钟。在此期间,表单的客户端区域仍然没有完全绘制,这对用户来说很难看。在这段阻塞时间内,我不需要程序响应单击或其他用户操作,因此不需要将阻塞操作放入线程中-我只需要完全绘制表单。因此,我使用TForm.Update和Application ProcessMessages在阻塞操作之前更新表单,效果非常好:

procedure TForm1.FormActivate(Sender: TObject);
begin
  Form1.Update;
  Application.ProcessMessages;
  Sleep(7000);
end;

然而,我想知道是否有另一个更优雅的解决方案来解决这个问题。例如,这可能是在TForm的子代中实现的OnShown事件,该事件将在表单完全绘制后激发。如何实现这样的事件?

您真正的问题是您正在阻塞UI线程。简单地说,你决不能那样做。将长时间运行的任务移动到不同的线程上,从而使UI保持响应

如果要查找在应用程序完成加载/重新绘制时触发的事件,则应使用TApplication.OnIdle event

一旦读取应用程序以接收用户输入,就会触发此事件。注意,每次应用程序空闲时都会触发此事件,所以您需要实现一些控制变量,当OnIdle第一次被触发时,该变量将通知您


但是正如David已经指出的,阻止UI主线程是不好的。为什么?当您阻塞主线程时,应用程序无法正常处理其消息。这可能导致操作系统将您的应用程序识别为挂起。aou明确希望避免这种情况,因为它可能会导致用户去强制杀死你的应用程序,这可能会导致数据丢失。此外,如果您想为Windows以外的任何其他平台设计应用程序,您的应用程序可能会因此而无法通过认证程序。

在过去,一个简单的PostMessage就可以做到这一点。 基本上,您可以在基本窗体的DoShow期间激发它:

procedure TBaseForm.DoShow;
begin
  inherited;
  PostMessage(Handle, APP_AFTERSHOW, 0, 0);
end;
然后捕获消息并为从该基本表单继承的所有表单创建一个AfterShow事件

但这不再有效,如果您正在蒙皮并且拥有大量VCL控件,那么就不起作用了

我的下一个技巧是在DoShow中生成一个简单线程,并检查IsWindowVisibleHandle和IsWindowEnabledHandle。这真的加快了速度,因为db打开和其他东西已经在事后展示事件中,它从加载时间缩短了250ms

最后,我想到了madHooks,它非常容易为我的应用程序钩住API ShowWindow,并从此启动应用程序

function ShowWindowCB(hWnd: HWND; nCmdShow: Integer): BOOL; stdcall;
begin
  Result := ShowWindowNext(hWnd, nCmdShow);
  PostMessage(hWnd, APP_AFTERSHOW, 0, 0);
end;

procedure TBaseForm.Loaded;
begin
  inherited;
  if not Assigned(Application.MainForm) then // Must be Mainform it gets assigned after creation completes
    HookAPI(user32, 'ShowWindow', @ShowWindowCB, @ShowWindowNext);
end;
要使整个过程在结束之前完全绘制,它仍然需要一个ProcessPaintMessages调用

procedure TBaseForm.APPAFTERSHOW(var AMessage: TMessage);
begin
  ProcessPaintMessages;
  AfterShow;
end;

procedure ProcessPaintMessages; // << not tested, pulled out of code
var
  msg: TMsg;
begin
    while PeekMessage(msg, 0, WM_PAINT, WM_PAINT, PM_REMOVE) do 
      DispatchMessage(msg);
end; 

看看这里的例子。但我相当肯定在堆栈溢出上有一个问题。在IMO提供的链接中发布消息并不完全可靠,因为在某些情况下,AFAIK消息也可能丢失。我需要的东西是200%可靠。信息不会丢失。但是,如果队列已满,则在排队时PostMessage可能会失败,您可以通过PostMessage结果参数检测到。在.NET中有一个表单。显示的事件请参阅MSDN。因此,在Windows级别,窗体必须知道何时完成绘制。难道不能用Windows API访问它吗?你在错误的地方阻塞了。只需在FormShow事件结束时向自己发送一条消息,并对该消息执行阻止操作。当您在表单的处理程序中收到消息时,表单将被完全绘制。当窗体/应用程序失去焦点并重新获得焦点时,可以多次触发OnActivate;不管怎么说,这通常是一个错误的地方;但在这种情况下,出于我的特殊目的,这不是问题。显然,如果您希望应用程序无法抽取其UI消息队列,并且系统将窗口重设为无响应,则由您决定。我不会这样做,这就是我写上述内容的原因。但如果你愿意的话,这显然是你的选择。OnIdle也不是海报想要的。它的触发频率太高,甚至比使用OnActivate更糟糕。并不是所有的东西都可以移动到背景线程;例如,在DBGrid中显示数据的CRUD应用程序必须首先连接到DB,并且DB连接必须属于主线程才能访问。您不能将该连接移动到辅助线程,因为这样您的UI就无法访问该连接。
procedure TMainForm.AfterShow;
begin
  inherited;
  Sleep(8*1000);
 ......