【问题标题】:Why is MessageDlg not shown after a form is freed via OnClose event (in a thread)为什么通过 OnClose 事件(在线程中)释放表单后没有显示 MessageDlg
【发布时间】:2015-03-31 23:24:19
【问题描述】:

鉴于这种情况:

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 变量的范围可能导致了问题,但将 Form 声明为 TForm2 实例中的私有字段并不能解决问题。

我的目标是显示一个启动表单,执行我的线程,然后通过所述线程的回调,关闭启动表单并在适合用户的地方显示对话框。

为了清楚起见,为什么会发生这种情况?

【问题讨论】:

  • 如果你看,我正在调用 TThread.Synchronize...除非我不能这样称呼它,否则接受道歉

标签: multithreading delphi dialog delphi-xe6


【解决方案1】:

Windows 运行时要求可视窗口的消息由在创建该窗口的同一线程中运行的消息循环处理。

Windows API 还强制执行有关可以从创建窗口的线程以外的线程对窗口执行哪些操作的规则。即确实很少,除了向它发送或发布消息。

考虑到这些信息,我们可以解释您的情况。

Form.Close 方法最终通过向表单发布消息 (CM_RELEASE) 来关闭表单。但在您的实现中,负责响应该消息的消息循环 - 应用程序主消息循环 - 由于消息是从 Synchronize() 方法中发布的事实而被阻止。 p>

即您的 Synchronize()d 方法发布消息以关闭表单,但该消息不能也不会由该表单窗口处理,直到您的 Synchronize()d 方法完成,并且在用户响应消息之前不会发生您正在使用该方法呈现的框。

我希望这可以帮助您了解代码中发生的情况。

【讨论】:

  • 我明白你在说什么,谢谢。但是,不确定我是否理解它如何适用于我的问题。表格不关闭不是问题。事实上 MessageDlg 根本没有显示,所以它没有等到用户对消息框做出响应,首先没有消息框。那么 Form.Close 方法(通过 CM_RELEASE 消息)是否干扰了 MessageDlg(表单)?我错过了重点吗?
  • 我明白了——对不起,我有点误解了。但正如大卫在他的回答中强调的那样,根本问题仍然是哪个线程“拥有”所涉及的窗口。作为一般规则,我会说最好将所有 UI 交互保留在主线程中,并确保任何其他线程完全不可见(并且不要使用 Synchronize() - 这是魔鬼的工作)。它鼓励人们相信它可以解决所有 UI/线程问题,但实际上它没有。了解并使用事件、互斥锁等适当的线程同步以及 Windows 消息在存在线程的情况下如何工作。
  • 我认为这并不重要。没有线程的应用也会出现完全相同的问题。
  • @David,Ack,当然可以。由于 ShowModal 消息循环,重入是问题而不是线程。
【解决方案2】:

正在发生的事情是对话框的所属窗口是正在关闭的窗体。当对话框开始它的模态消息循环时,窗体被释放并与它一起删除它拥有的窗口。包括对话框。

对此进行测试,让您更有信心相信我上面所说的内容是正确的,方法是将首先显示对话框的调用替换为

MessageBox(0, ...);

然后用

MessageBox(Form.Handle, ...);

也就是说,明确对话框的所有者。

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

【讨论】:

  • 我追踪并验证了这种情况。调试器修改TCustomTaskDialog.Execute中的LParentWnd为0,出现对话框。否则,对话框将活动窗口作为其所有者,然后将其拉到它的脚下。
  • @David,既然您的预感已被证明是准确的,我是否建议您编辑您的答案以消除明显的不确定性并更清楚地表明情况确实如此? (我不想自己编辑它)。 :)
  • 只是张贴者没有错过明显的解决方案:在Form.Close 之后调用SetFocus,当显示消息对话框时,活动窗口将是问题中的TForm2 实例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-10-27
  • 2011-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多