代码之家  ›  专栏  ›  技术社区  ›  robsoft

在不调用application.processmessages的情况下让窗口刷新(等)?

  •  12
  • robsoft  · 技术社区  · 16 年前

    我这里有一个传统的应用程序,它有一些“耗时”的循环,这些循环由于各种用户交互而被触发。耗时的代码周期性地用进度信息(通常是一个标签)更新屏幕上的某些内容,然后,似乎是为了说服在那里进行视觉刷新,然后,代码调用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;
    

    我已经能听到你们都晕倒了,在后面。-)

    5 回复  |  直到 11 年前
        1
  •  18
  •   mghie    16 年前

    如果你打电话 UpDead() 在更改了属性之后,您将强制对控件重新绘制。另一种方法是打电话 再着色() 而不是 刷新() ,这意味着调用 UpDead() .

    你可能需要打电话 UpDead() 在父控件或帧上,但这可以使您消除 处理消息() 完全呼叫。

        2
  •  8
  •   Wim ten Brink    16 年前

    我用于长时间更新的解决方案是在单独的线程中进行计算。这样,主线程就保持了很好的响应性。一旦线程完成,它会向主线程发送一条Windows消息,指示主线程可以处理结果。

    不过,这还有一些其他严重的缺点。首先,当另一个线程处于活动状态时,您必须禁用一些控件,因为它们可能会再次重新启动线程。 第二个缺点是您的代码需要成为线程安全的。有时候这是一个真正的挑战。如果您使用的是遗留代码,那么您的代码很可能不会是线程安全的。 最后,多线程代码更难调试,应该由经验丰富的开发人员完成。

    但是多线程的最大优点是应用程序保持响应性,用户只需继续做其他一些事情,直到线程完成。基本上,您正在将同步方法转换为异步函数。线程可以触发多条消息,指示某些控件刷新自己的数据,这些消息将即时更新。(在您希望更新它们的时候。)

    我自己也用过很多次这种技术,因为我觉得它好多了。(但也更复杂。)

        3
  •  3
  •   Le Minh Hoang    13 年前

    不要调用application.processmessages,这很慢,可能会生成无限递归。 例如,要在不使用flick的情况下更新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;
    

    为了获得更好的性能,应该先创建缓存位图,然后在进程完成后释放它。

        4
  •  1
  •   user114285    16 年前

    你要找的技术叫做 螺纹加工 . 这是编程中的一种困难技术。代码应该小心处理,因为它很难调试。无论您是否使用线程,都应该绝对禁用用户应该使用的控件 乱七八糟 (我是指能够影响正在进行的过程的控制措施)。你应该考虑使用 行动 启用/禁用按钮等…

        5
  •  1
  •   Re0sless    16 年前

    我们在表单中有一个布尔变量,而不是禁用控件。 忙的 ,然后在用户按下按钮时简单地检查一下,我们介绍它是因为您提到的确切原因,用户在等待长时间运行的代码运行时单击按钮(您的代码示例非常熟悉)。

    所以你最终会得到

    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块中是不必要的,因为最终会得到一个不起作用的表单。

    正如建议的那样,如果它用于不会影响数据的代码,比如运行报告或数据分析,我们确实使用线程,但是有些事情这不是一个选项,比如如果我们正在更新20000个产品记录,那么我们不希望任何人试图在中途出售或以其他方式更改记录,因此我们必须阻止应用程序,直到它完成。