我在这里有一个遗留应用程序,它有一些“耗时”的循环,由于各种用户交互而被解雇。耗时的代码定期用屏幕上的进度信息(通常是标签)更新某些东西,然后,似乎说服视觉刷新发生在那里,然后,代码调用Application.ProcessMessages(argh!)。
我们现在都知道这可以给GUI应用程序带来什么样的麻烦(慈善,那时候是一个更无辜的时间)而且我们发现确实像鸡蛋一样,我们不时会让用户实现该计划不可能,因为他们在节目“忙碌”时点击控件。
定期刷新表单的视觉效果而不接受其他事件/消息等的最佳方法是什么?
我的想法是;
- 在做任何耗费时间之前禁用所有控件,并保留'... ProcessMessages'调用以“强制”刷新,或者
- 找到另一种定期刷新控件的方法
我可以做前者,但它让我想知道 - 有更好的解决方案吗?
来自遗产的示例代码;
i:=0; while FJobToBeDone do begin DoStepOfLoop; inc(i); if i mod 100 = 0 then begin UpdateLabelsEtc; Application.ProcessMessages; end; end;
我已经听到你们都在晕倒了。 : - )
答案 0 :(得分:18)
如果在更改属性后在控件上调用 Update(),则会强制它们重绘。另一种方法是调用 Repaint()而不是 Refresh(),这意味着调用 Update()。
您可能还需要在父控件或框架上调用 Update(),但这可以让您完全消除 ProcessMessages()调用。
答案 1 :(得分:8)
我用于长时间更新的解决方案是在单独的线程中进行计算。这样,主线程保持响应。线程完成后,它会向主线程发送一条Windows消息,指示主线程可以处理结果。
但这有一些其他严重的缺点。首先,当另一个线程处于活动状态时,您必须禁用一些控件,因为它们可能会再次重新启动线程。 第二个缺点是您的代码需要成为线程安全的。这有时可能是一个真正的挑战。如果您正在使用遗留代码,那么您的代码很可能不是线程安全的。 最后,多线程代码更难调试,应该由经验丰富的开发人员完成。
但多线程的一大优势是您的应用程序保持响应,并且用户可以继续执行其他操作,直到完成该线程。基本上,您正在将同步方法转换为异步函数。并且线程可以触发几条指示某些控件的消息来刷新它们自己的数据,这些数据会立即更新。 (并且在您希望更新它们的那一刻。)
我自己使用过这种技术很多次,因为我觉得它好多了。 (但也更复杂。)
答案 2 :(得分:3)
不要调用Application.Processmessages,这很慢并且可能会生成无限递归。 例如,要在没有轻弹的情况下更新Panel1中的所有内容,我们可以使用此方法:
procedure TForm1.ForceRepaint;
var
Cache: TBitmap;
DC: HDC;
begin
Cache := TBitmap.Create;
Cache.SetSize(Panel1.Width, Panel1.Height);
Cache.Canvas.Lock;
DC := GetDC(Panel1.Handle);
try
Panel1.PaintTo(Cache.Canvas.Handle, 0, 0);
BitBlt(DC, 0, 0, Panel1.Width, Panel1.Height, Cache.Canvas.Handle, 0, 0, SRCCOPY);
finally
ReleaseDC(Panel1.Handle, DC);
Cache.Canvas.Unlock;
Cache.Free;
end;
end;
为了获得更好的性能,应首先创建缓存位图,并在流程完成时自由创建
答案 3 :(得分:1)
您正在寻找的技术称为线程。这是编程中的一种难以理解的技术。应谨慎处理代码,因为它更难调试。无论您是否使用线程,您都应该禁用用户不 搞乱的控件(我的意思是可以影响正在进行的过程的控件)。您应该考虑使用操作启用/禁用按钮等...
答案 4 :(得分:1)
我没有禁用控件,而是以 FBusy 的形式使用布尔变量,然后只需在用户按下按钮时进行检查,我们会根据您提到的确切原因进行介绍,用户点击按钮当他们等待长时间运行的代码运行时(可怕的代码示例是多么熟悉)。
所以你最终会得到像
这样的东西procedure OnClick(Sender:TObejct);
begin
if (FBusy) then
begin
ShowMessage('Wait for it!!');
Exit;
end
else FBusy := True;
try
//long running code
finally
FBusy := False;
end;
end;
如果出现退出或异常,它会无法记住在try-finally块中说出长时间运行的代码,因为你最终会得到一个不起作用的表单。
正如所建议的那样,我们确实使用线程,如果它的代码不会影响数据,比如运行报告或数据分析,但有些事情这不是一个选项,比如我们要更新20,000个产品记录,那么我们就不要我希望任何人试图在飞行途中出售或其他明智地改变记录,因此我们必须阻止该应用程序直到完成。