当通过OnClose事件(在线程中)释放一个窗体后,为什么MessageDlg没有显示?

4

考虑以下情景:

procedure TForm2.Button1Click(Sender: TObject);
var
  Form: TForm;
begin
  Form := TForm.Create(nil);
  Form.OnClose := FormClosed;
  Form.Show;
  Sleep(200);
  TThread.CreateAnonymousThread(
    procedure
    begin
      TThread.Synchronize( nil, 
        procedure 
        begin 
          Form.Close; 
          MessageDlg('Testing', mtInformation, [mbok], 0); 
        end);
    end).Start;
end;

procedure TForm2.FormClosed(Sender: TObject; var Action: TCloseAction);
begin
  Action := TCloseAction.caFree;
end;

我的MessageDlg调用没有显示(该调用的结果始终是mrCancel(2))。
经过挖掘,与OnClose事件和将Action设置为caFree有关。将Form.Close更改为Form.Free并完全删除OnClose事件将显示MessageDlg ok。在调用Form.Close之前放置MessageDlg可以正常工作。最初我认为我的Form变量的范围可能会导致问题,但在TForm2实例中声明Form为私有字段并不能解决问题。
我的目标是显示一个闪屏窗体,执行我的线程,然后通过此线程的回调关闭Splash窗体并在适当时向用户显示对话框。
为了清楚起见,为什么会发生这种情况?

如果你看一下,我正在调用TThread.Synchronize... 除非我不能这样调用,否则请接受我的道歉。 - Jason
2个回答

7
发生的情况是对话框所属的窗口是正在关闭的表单。当对话框开始其模态消息循环时,该表单被释放并将其拥有的窗口一起关闭。包括对话框。
为了让你更加确信我上面所说的是正确的,请测试一下,将首先调用显示对话框的语句替换为:
MessageBox(0, ...);

然后通过技术手段实现
MessageBox(Form.Handle, ...);

也就是说,要明确对话框的所有者。
第一个版本没有所有者,将显示对话框。第二个版本不会显示,因为它复制了您代码中的场景。

4
我已经追踪和验证了这一情况。通过调试器修改TCustomTaskDialog.Execute中的LParentWnd为0,对话框将被显示出来。否则,对话框将以活动窗口作为其所有者,并且该窗口将被遮盖。 - Sertac Akyuz
@David,既然你的直觉被证明是正确的,我可以建议你编辑你的答案,消除明显的不确定性,并更清楚地表明这确实是情况吗?(我不想自作主张去编辑它)。 :) - Deltics
1
只是为了让发帖者不错过显而易见的解决方案:在 Form.Close 后放置一个 SetFocus 调用,当消息对话框显示时,活动窗口将是问题中的 TForm2 实例。 - Sertac Akyuz

3
Windows运行时要求将可视窗口的消息处理通过在同一线程中创建该窗口的消息循环来运行。Windows API还强制执行关于可以从与创建窗口的线程不同的线程对窗口执行哪些操作的规则。也就是说,除了向其发送或发布消息外,几乎无法执行其他操作。考虑到这些信息,我们可以解释发生在您的情况中的情况。Form.Close方法最终通过发布消息(CM_RELEASE)关闭表单。但在您的实现中,负责响应该消息的消息循环 - 应用程序主消息循环 - 由于该消息是从Synchronize()方法内部发布的而被阻止。也就是说,您的Synchronize()方法将消息发布到关闭窗体,但是在Synchronize()方法完成之前,该消息不能且不会被该窗体的窗口处理,而这将不会发生,直到用户响应您在该方法中呈现的消息框为止。希望这可以帮助您理解您的代码中发生了什么。

3
我明白你的意思,谢谢。然而,我不确定它如何适用于我的问题。表单没有关闭不是问题,问题在于MessageDlg根本没有显示,所以它没有等待用户响应消息框,因为首先就没有消息框。那么是Form.Close方法(通过CM_RELEASE消息)干扰了MessageDlg(表单)吗?我有遗漏吗? - Jason
我明白了,抱歉,我有点误解了。但正如David在他的回答中所强调的那样,根本问题仍然是哪个线程“拥有”涉及到的窗口。一般来说,我认为最好将所有UI交互保留在主线程中,并确保任何其他线程完全不可视(并且不使用Synchronize() - 它是魔鬼的作品)。它鼓励人们相信它可以解决所有UI /线程问题,但实际上并非如此。学习并使用适当的线程同步,使用事件、互斥等以及了解Windows消息在存在线程时的工作原理。 - Deltics
我认为那并不是很相关。在没有线程的应用程序中也会出现完全相同的问题。 - David Heffernan
@David,确认,当然可以。问题在于可重入性而不是线程,这要归功于ShowModal消息循环。 - Deltics

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接