在程序启动时,在OnActivate
事件处理程序中,我需要做一些阻止程序几秒钟的事情。在此期间,表单的客户区仍未完全绘制,这对用户来说看起来很难看。 (在此阻塞时间内,我不需要程序来响应点击或其他用户操作,因此不需要将阻塞操作放入线程中 - 我只需要完全绘制表单)。因此,我使用TForm.Update
和Application-ProcessMessages
在阻止操作之前更新表单,该操作非常有效:
procedure TForm1.FormActivate(Sender: TObject);
begin
Form1.Update;
Application.ProcessMessages;
Sleep(7000);
end;
然而,我想知道是否没有另一个更优雅的解决方案来解决这个问题。这可能是例如在TForm的后代中实现的OnShown
事件,该事件将在表单完全绘制后触发。怎么可以实施这样的事件?
答案 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);
......