从外部关闭模态窗体并打开新的模态窗体

4
在我的主表单中,我有一个按钮,可以打开一个模态Form2(该表单可能会打开其他模态形式)。在打开Form2之前,我设置了一个计时器,该计时器将以编程方式关闭所有活动的模态表单(Form2.Close),并打开一个新的模态Form3
问题是,当以模态方式打开Form3时,Form2仍然保持(可见),只有当我通过单击X关闭Form3时,Form2才会关闭。
为了重现添加3个表单到项目中添加一个TButton,并在Form1(主表单)上放置一个TTimer
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Timer1: TTimer;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
  public
  end;

var
  Form1: TForm1;

implementation

uses Unit2, Unit3;

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
begin
  Timer1.Enabled := False;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Timer1.Enabled := True;
  with TForm2.Create(Application) do
  try
    ShowModal;
  finally
    Free;
  end;
end;

procedure CloseActiveModalForms;
var
  I: Integer;
  F: TCustomForm;
  L: TList; // list of modal forms
begin
  L := TList.Create;
  try
    for I := 0 to Screen.CustomFormCount - 1 do
    begin
      F := Screen.CustomForms[I];
      if (fsModal in F.FormState) then
        L.Add(F);
    end;
    for I := 0 to L.Count - 1 do
      TCustomForm(L.Items[I]).Close; // this sets ModalResult := mrCancel
  finally
    L.Free;
  end;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Timer1.Enabled := False;
  CloseActiveModalForms; // this should close TForm2 but it does not.

  with TForm3.Create(Application) do // create new Modal TForm3
  try
    ShowModal;
  finally
    Free;
  end; 
end;

end.

为什么Form2没有关闭?为什么在我调用CloseActiveModalForms后,Form2的模态循环没有退出?

1个回答

5

您的调用栈如下所示:

1 Form1.Button1Click
2 Form2.ShowModal //Local message processing loop until form closes
3 Form1.Timer1Timer //Here you attempt to close the form
                    //but it doesn't actually until ShowModal exits
4 Form3.ShowModal // Another message loop that doesn't return until form closes

基本上,只有在Form3关闭后才能完成Form2的关闭。请注意,ShowModal是一个阻塞调用,用于显示一个窗体。如果你只是展示Form3(即不使用ShowModal),该调用不会阻塞,你将看到随着调用堆栈的解除,Form2可以关闭。
你应该通过延迟展示Form3来解决这个问题,直到Form2关闭。OnFormDestroyEvent应该足够(不幸的是我无法测试它)。
procedure TForm1.ShowForm3(Sender: TObject);
var
  LForm: TForm;
begin
  LForm := TForm3.Create(Application); //as you created it, but nil owner should suffice
  try
    LForm.ShowModal;
  finally
    LForm.Free;
  end; 
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Timer1.Enabled := False;
  //You will need to figure out how you reference the Form2 instance.
  Form2.OnFormDestroy := ShowForm3;
  CloseActiveModalForms;
  //Form2 will close after you backtrack up the call-stack.
  //When it's destroyed, your event handler will create and show a TForm3 instance.
end;

请注意上述内容只是展示了该概念。根据您的最终目标,您需要设计更加稳健的方法。

然而,我建议过度使用模态表单通常在用户体验方面被认为不友好。


"Form1.Timer1Timer // 这里你试图关闭窗体,但实际上直到ShowModal退出后才会执行"这正是我的问题所在:为什么Form2没有存在ShowModal的方法?模态循环检查ModalResult <> 0,而调用Close会设置ModalResult := mrCancel。即使我在调用CloseActiveModalForms之后调用Application.ProcessMessages,循环也不会退出。我无法弄清楚原因。 - zig
1
@zig。这就是模态窗体的关键所在。它们挂起了其他窗体的活动过程。这就是为什么你不能单击主窗体 - 或更具体地说,主窗体直到模态窗体退出才会响应。form.Close只是开始关闭进程 - 但在该进程完成之前你又显示了另一个模态窗体。因此,它只能在新模式窗体自我关闭后完成。 - Dsm
1
好的,我想我明白了。我之前没有意识到Timer1Timer实际上是在本地模态消息循环中处理的。因此,使用PostMessage延迟调用显示Form3是可行的 - 这样可以让Form2的本地消息循环退出,并关闭模态窗体。 - zig
@zig - 在 Close 调用之后,您还可以添加 TCustomForm(L.Items[I]).Hide;。模态窗体将被延迟释放,但如果在 Form3 中有检查它们的代码,则模态结果将被设置。不过,您的解决方案也是可以的。在此处没有风险,因为它们都是用户消息并具有相同的优先级,但不要忘记 Close 也会发布一个消息 (CM_RELEASE)。 - Sertac Akyuz
@SertacAkyuz,感谢您。我实际上是在 Close 之前添加了 Hide。我非常确定除非您在关闭操作中显式指定一个 caFree(请参见 TCustomForm.CloseModal),否则默认情况下 Close 不会调用 ReleaseShowModal;...finally Free; 是释放模态窗体。 - zig
@zig - 在关闭之前隐藏也应该可以。当然,关于caFree/Release,你是正确的。 - Sertac Akyuz

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