-1

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

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

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

4

3 回答 3

2

您真正的问题是您阻塞了 UI 线程。简而言之,你绝不能这样做。将长时间运行的任务移至不同的线程,从而使 UI 保持响应。

于 2014-06-17T23:19:50.270 回答
0

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

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

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

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

于 2014-06-18T09:15:21.913 回答
0

在过去,一个简单的 PostMessage 就可以解决问题。本质上,您在基本表单的 DoShow 期间触发它:

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

然后捕获消息并为从该基本表单继承的所有表单创建一个 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,并看到由于 AfterShow 事件尚未完成而完全用空 db 容器绘制的表单。

procedure TMainForm.AfterShow;
begin
  inherited;
  Sleep(8*1000);
 ......
于 2015-06-26T00:34:11.707 回答