我的应用程序布局基于左侧的treeView和右侧的面板。该面板托管一个不同的TForm类,具体取决于所选的树节点(一种'形式浏览器')。一次只显示一个表单,它暴露存储在别处的基础数据,并在每个新的树节点点击时创建和销毁表单实例。
除了以下场景外,这一切都正常。单击表单上的一个按钮,该按钮启动需要一秒左右的操作。在此操作期间,可能会调用Application.ProcessMessages。现在,在此操作实际完成之前,用户单击一个新的树节点。处理此wmMousedown消息,导致表单立即释放。然后,操作代码返回到表单代码,以查找自己已更改并导致AV。
我的问题是,在我允许表单被释放之前,有没有办法知道表单的消息已经处理完毕?单击关闭按钮时,模态窗体似乎会执行此操作,因为如果忙,它们会在关闭之前暂停...
由于 布赖恩
答案 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中的“数据”指针)。