2

我有一个基于左侧树视图和右侧面板的应用程序布局。面板根据所选的树节点(一种“表单资源管理器”)托管不同的 TForm 类。一次只显示一个表单,它暴露了存储在其他地方的基础数据,并且在每个新的树节点单击时创建和销毁表单实例。

除了以下情况外,这一切都很好。单击表单上的一个按钮,该按钮启动一个需要一秒钟左右的操作。在此操作期间,可能会调用 Application.ProcessMessages。现在就在此操作实际完成之前,用户单击一个新的树节点。处理此 wmMousedown 消息会导致立即释放表单。动作代码然后返回到表单代码中发现 self 发生了变化并导致了 AV。

我的问题是,在我允许释放表单之前,有没有办法知道表单的消息已全部处理并完成?单击关闭按钮时,模态表单似乎会执行此操作,因为如果忙,它们会在关闭之前暂停...

谢谢布赖恩

4

5 回答 5

7

不惜一切代价避免使用Application.ProcessMessages !

我看到的最常见的不恰当用法是允许重新绘制 GUI,例如在更新标签标题之后。确保 GUI 更新的最安全方法是使用Update方法显式重绘任何受影响的控件(该方法绕过绘制消息并导致控件直接重绘)。或者它可能是Refresh方法 - 或者它可能是一个或两个!很遗憾地说我永远记不起我的头顶!

正如您所发现的那样, Application.ProcessMessages会导致“重入”问题,从而有效地在代码中创建潜在的新的、短暂的、“主消息循环”,从而导致难以诊断和难以重现问题。

在这种情况下,我将检查Application.ProcessMessages的使用,看看是否无法设计一种替代方法来消除它在您的代码中的使用,而不是尝试修补许多其他代码以使其解决Application.ProcessMessages的问题.ProcessMessages原因。

注意:该规则的一个例外是使用Application.ProcessMessages来保持“进度”消息/对话框上的“取消”按钮中的响应性是相对安全的,只要该进度消息/对话框是模态的并且您的其余部分显示该对话框时应用程序被有效禁用,因此只有“取消”按钮可以响应任何消息

于 2009-09-09T11:48:08.407 回答
4

要回答最后一段中的实际问题...... :)

如果您在表单上调用Release,则会向表单发送一条消息,这将导致它在收到该消息时自行释放。

由于消息被发布到消息队列中,它只会在处理该表单的任何/所有其他当前消息后到达,完全符合我的要求。

于 2009-09-09T11:59:38.037 回答
1

在每个放置表单的类中实现 IsBusy 属性(使用继承 - 在父表单中实现此属性)。在从托管面板中删除表单之前(无论是通过调用 Free 还是简单地从面板中移动),检查它的 IsBusy 是否不正确。如果您的托管表单正忙,请等到它完成,然后将其删除。您甚至可以添加一些方法来通知托管表单,它应该中止其长时间运行的任务。

它不仅可以帮助您解决当前的问题,而且您还可以清理表单中的一些业务逻辑。

因此,TreeView 中的表单更改代码应包含以下代码:

    {FCurrentForm is a reference to currently placed form on panel}
    if (FCurrentForm.IsBusy) do
    begin
      {remember some information that will be used to create new form}
      FNewFormToBeAdded := ... 
    end
    else
    begin
      FreeCurrentForm();
      PlaceNewFormOnPanel();
    end;

因此,您应该有一些例程,例如:

   procedure THostForm.NotifyMeAboutTaskFinished;
   begin
     if FNewFormToBeAdded <> 0 than 
     begin
       FreeCurrentForm();
       PlaceNewFormOnPanel();
     end; 
   end;

在您的托管形式中,您可以拥有

procedure TSomeHostedForm.btnDoLongTaskClick(Sender : TObject);
begin
  IsBusy := true;
  try
    {... do some tikme taking task ...}
  finally
    IsBusy :=false;
    NotifyHostingFormIAMNotBusyAnymore();
  end;
end;
于 2009-09-09T11:32:53.950 回答
0

您的 TreeView.OnClick 可以调用当前活动表单的 CloseQuery 方法。如果 CloseQuery 返回 false,则不要交换表单。然后,您可以在需要它的表单上处理标准 CloseQuery。

在面板中显示的表单上,您需要跟踪某些状态以了解您是否真的可以关闭。我有我长时间运行的进程也检查停止条件。我通常也有一个取消按钮,但是对 CloseQuery 的任何调用也会导致长时间运行的进程中止。我的 CloseQuery 通常如下所示:

procedure TBatchPoster.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin inherited;

  if FProcessRunning = true then
  begin
    FStop := true;
    CanClose := false;
  end;
end;

通常,如果用户单击一次并且没有任何更改,那么当他们再次尝试单击时,长时间运行的进程已停止并且第二次单击成功更改了表单。

确实这与 smok1 的答案相同,但由于您已经在使用表单,因此不需要添加新属性。

于 2009-09-09T18:49:57.210 回答
0

虽然我同意“Deltics”的两个答案,但还有另一种选择 - 取决于您是否需要释放表格。

在 FormClose 事件表单上,将 Action 设置为 caHide。这将隐藏而不是破坏表单。您将需要跟踪已分配的表单(可能使用 TTreeNode 中的“数据”指针)。

于 2009-09-09T22:05:00.420 回答