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

时间:2009-09-09 11:27:32

标签: delphi message-queue tform

我的应用程序布局基于左侧的treeView和右侧的面板。该面板托管一个不同的TForm类,具体取决于所选的树节点(一种'形式浏览器')。一次只显示一个表单,它暴露存储在别处的基础数据,并在每个新的树节点点击时创建和销毁表单实例。

除了以下场景外,这一切都正常。单击表单上的一个按钮,该按钮启动需要一秒左右的操作。在此操作期间,可能会调用Application.ProcessMessages。现在,在此操作实际完成之前,用户单击一个新的树节点。处理此wmMousedown消息,导致表单立即释放。然后,操作代码返回到表单代码,以查找自己已更改并导致AV。

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

由于 布赖恩

5 个答案:

答案 0 :(得分:7)

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

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

Application.ProcessMessages 导致您发现的“重新入侵”问题,在您的代码中有效地创建潜在的新的,短暂的“主要消息循环”,这可能导致难以诊断和难以重现问题。

我会检查在这种情况下使用 Application.ProcessMessages ,看看是否无法设计替代方法来消除代码中的使用,而不是尝试修补大量的其他代码只是为了解决 Application.ProcessMessages 导致的问题。

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

答案 1 :(得分:4)

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

如果您在表单上调用发布,则会向表单发布一条消息,当收到该消息时,该消息将导致免费

由于消息发布编辑到消息队列,因此在处理完该表单的任何/所有其他当前消息后,它将仅 <到达,整齐地实现了你问我想。

答案 2 :(得分: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;

答案 3 :(得分: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;

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

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

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

答案 4 :(得分:0)

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

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