动态Delphi表单创建-确保正确的鼠标消息处理

动态Delphi表单创建-确保正确的鼠标消息处理,delphi,message-queue,tform,Delphi,Message Queue,Tform,我在左边有一个基于树视图的应用程序布局,在右边有一个面板。该面板根据所选的树节点(一种“表单浏览器”)承载不同的TForm类。一次只显示一个表单,它公开存储在其他位置的基础数据,并且在每次单击新的树节点时创建和销毁表单实例 除了以下场景之外,这一切都可以正常工作。单击窗体上的一个按钮,该按钮将启动一个花费大约一秒钟时间的操作。在此操作期间,可能会调用Application.ProcessMessages。现在,就在该操作实际完成之前,用户单击一个新的树节点。处理此wmMousedown消息会立即

我在左边有一个基于树视图的应用程序布局,在右边有一个面板。该面板根据所选的树节点(一种“表单浏览器”)承载不同的TForm类。一次只显示一个表单,它公开存储在其他位置的基础数据,并且在每次单击新的树节点时创建和销毁表单实例

除了以下场景之外,这一切都可以正常工作。单击窗体上的一个按钮,该按钮将启动一个花费大约一秒钟时间的操作。在此操作期间,可能会调用Application.ProcessMessages。现在,就在该操作实际完成之前,用户单击一个新的树节点。处理此wmMousedown消息会立即释放表单。然后,操作代码返回到表单代码,以发现self已更改并导致AV

我的问题是,在我允许释放表单之前,是否有办法知道表单的消息已经全部处理并完成?模态窗体在单击“关闭”按钮时似乎会这样做,因为如果忙,它们会在关闭之前暂停

谢谢
Brian

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

它不仅有助于解决当前的问题,还可以清理表单中的一些业务逻辑

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

    {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;

避免使用应用程序。不惜一切代价处理消息

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

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

在这种情况下,我将检查Application.ProcessMessages的使用情况,并查看是否可以设计一种替代方法来从代码中消除它的使用,而不是试图修补大量的其他代码,只是为了让它处理应用程序.ProcessMessages引起的问题


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

以回答最后一段中的实际问题……)

如果在表单上调用Release,则会将消息发布到表单,从而在表单收到该消息时将其自身释放


由于消息是post发送到消息队列中的,因此只有在处理完该表单的任何/所有其他当前消息后,它才会到达,我认为这完全符合您的要求。

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

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


过程TBatchPoster.FormCloseQuery(发送方:ToObject;变量CanClose:Boolean);
开始
继承的

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

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


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

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


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

您的示例代码没有多大意义。要么在
IsBusy()
方法中执行代码,要么该方法的名称错误,要么需要删除
Sleep()
。或者代码是在另一个方法中执行的,那么它就根本不被调用。我正在考虑在TreeView.OnClick(或OnMouseDown)事件中使用这段代码。当用户单击树视图上的新节点时,我们应该检查是否可以在FCurrentForm上调用Free。如果FCurrentForm实际上很忙,我们应该等到FCurrentForm完成他的工作,而不是Free FCurrentForm,创建新表单(适合选择),并将FCurrentForm设置为指向新创建的表单。所以我认为不改变c
  if FProcessRunning = true then
  begin
    FStop := true;
    CanClose := false;
  end;
end;