代码之家  ›  专栏  ›  技术社区  ›  Brian Frost

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

  •  2
  • Brian Frost  · 技术社区  · 15 年前

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

    除以下情况外,这一切工作正常。单击窗体上的某个按钮,该按钮将启动一个大约需要一秒钟的操作。在此操作期间,可能会调用application.processMessages。现在,就在这个操作实际完成之前,用户单击一个新的树节点。将处理此wmmousedown消息,从而立即释放窗体。然后,操作代码返回到表单代码,发现self已更改并导致av。

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

    谢谢 布瑞恩

    5 回复  |  直到 15 年前
        1
  •  4
  •   Deltics    15 年前

    为了回答最后一段的实际问题…:)

    如果你打电话 释放 在表单上,这会向表单发送一条消息,从而导致 免费的 当它收到那条信息的时候。

    因为信息是 邮递 它将到达消息队列 只有 在处理了该表单的任何/所有其他当前消息之后,就可以很好地满足您的要求了。

        2
  •  7
  •   Deltics    15 年前

    避免使用 应用程序.processMessages 不惜一切代价!

    我看到的最常见的不恰当用法是允许重新绘制gui,例如在更新标签标题之后。确保gui更新的最安全方法是使用 更新 方法(它绕过绘制消息并导致控件直接重新绘制)。或者可能是 刷新 方法-或者它可以是其中之一,也可以两者都是!难过的说,我永远记不起来了!

    应用程序.processMessages 导致你发现的“重新入选”问题,有效地在代码中创建潜在的新的、短时间的“主消息循环”,这可能导致难以诊断和难以重现的问题。

    我会检查 应用程序.processMessages 在这种情况下,看看是否可以设计一种替代方法来从代码中消除它的使用,而不是尝试修补许多 其他 代码只是为了让它处理 应用程序.processMessages 原因。

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

        3
  •  1
  •   smok1    15 年前

    在每个放置的窗体的类中实现isbusy属性(使用继承-在父窗体中实现此属性)。在从宿主面板中删除窗体之前(无论是通过调用free还是仅仅从面板中移动),请检查其isbusy是否为true。如果宿主窗体正忙,请等待它完成,然后将其删除。您甚至可以添加一些方法来通知宿主窗体,它应该中止其长时间运行的任务。

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

    因此,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;
    
        4
  •  0
  •   Mark Elder    15 年前

    您的treeview.onclick可以调用当前活动窗体的closequery方法。如果closequery返回false,则不要交换表单。然后,您可以处理需要它的窗体上的标准闭包。

    在面板中显示的窗体中,您需要跟踪某个状态,以便知道是否真的可以关闭。我让我的长时间运行的进程也检查一个停止条件。我通常也有一个cancel按钮,但是对closequery的任何调用也会导致长时间运行的进程中止。我的closequery通常如下所示:

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

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

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

    实际上这和smok1的答案是一样的,但是由于您已经在使用一个表单,所以不需要添加新的属性。

        5
  •  0
  •   Gerry Coll    15 年前

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

    在formclose事件中,将操作设置为cahide。这将隐藏而不是破坏表单。您需要跟踪已分配的表单(可能使用ttreenode中的“data”指针)。