TForm的OnShown事件?

时间:2014-06-17 16:00:17

标签: delphi delphi-xe2 tform

在程序启动时,在OnActivate事件处理程序中,我需要做一些阻止程序几秒钟的事情。在此期间,表单的客户区仍未完全绘制,这对用户来说看起来很难看。 (在此阻塞时间内,我不需要程序来响应点击或其他用户操作,因此不需要将阻塞操作放入线程中 - 我只需要完全绘制表单)。因此,我使用TForm.UpdateApplication-ProcessMessages在阻止操作之前更新表单,该操作非常有效:

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

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

3 个答案:

答案 0 :(得分:2)

您真正的问题是您正在阻止UI线程。简而言之,你绝不能这样做。将长时间运行的任务移动到不同的线程上,从而允许UI保持响应。

答案 1 :(得分:0)

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

http://docwiki.embarcadero.com/Libraries/XE3/en/Vcl.Forms.TApplication.OnIdle

一旦读取应用程序以接收用户输入,就会触发此事件。注意每次应用程序进入空闲状态时都会触发此事件,因此您需要实现一些controll变量,当OnIdle第一次被触发时,它会通过电话给您打电话。

但正如大卫已经指出的那样阻止你的UI(主线程)是不好的。为什么?当您阻止主线程时,应用程序无法正常处理其消息。这可能会导致操作系统将您的应用程序识别为" Hanged"。并且你肯定想避免这种情况,因为它可能导致用户强行杀死你的应用程序,这可能会导致数据丢失。此外,如果您想为Windows以外的任何其他平台设计应用程序,您的应用程序可能会因此失败。

答案 2 :(得分:0)

过去,一个简单的PostMessage就可以了。 基本上你在基本形式的DoShow期间解雇它:

procedure TBaseForm.DoShow;
begin
  inherited;
  PostMessage(Handle, APP_AFTERSHOW, 0, 0);
end;

然后捕获msg并为从此基本表单继承的所有表单创建一个AfterShow事件。

但是,如果您正在换肤并拥有大量的VCL控件,那就不再有用了。

我的下一个技巧是在DoShow中生成一个简单的线程并检查IsWindowVisible(Handle)和IsWindowEnabled(Handle)。自从数据库打开以及其他内容已经在AfterShow活动中以来,它加速了250毫秒。

然后我终于想到了madHooks,很容易将API ShowWindow挂钩到我的应用程序并从中激活APP_AFTERSHOW。

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;

要在AfterShow之前完成整个绘制,它仍然需要一个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; 

我的最后一个测试是向AfterShow事件添加一个Sleep,并且看到Form完全用空数据库容器绘制,因为AfterShow事件尚未完成。

procedure TMainForm.AfterShow;
begin
  inherited;
  Sleep(8*1000);
 ......