【问题标题】:"Cannot create form. No MDI forms are currently active" error“无法创建表单。当前没有活动的 MDI 表单”错误
【发布时间】:2010-10-16 13:22:46
【问题描述】:

我有一个 MDI 主(父)窗体和一个 MDI 子窗体。我在运行时像这样创建孩子:

VAR
   FrmDereplic: TFrmDereplic;

procedure TMainFrm.Button2Click(Sender: TObject);
begin
 FrmDereplic:= TFrmDereplic.Create(MainFrm);
 FrmDereplic.Show;
end;

重现错误的步骤:
我启动应用程序,按按钮创建子项,按主(父)表单上的“x”按钮关闭应用程序,我收到“无法创建表单。当前没有 MDI 表单处于活动状态”错误。

出现错误的行在子窗体中:

procedure TFrmDereplic.FormClose(Sender: TObject; var Action: TCloseAction);
begin
 Action:= caFree;
end;

procedure TFrmDereplic.FormDestroy(Sender: TObject);
VAR MyIniFile: TCubicIniFile;
begin
 MyIniFile:= TCubicIniFile.Create(AppINIFile);
 TRY
  with MyIniFile DO
  begin
   if WindowState<> wsMaximized then
    begin
     // save form's screen pos
     ...
    end;
   WriteInteger  ('Dereplicator', 'fltExtensions', fltExtensions.ItemIndex);  <----- HERE
 FINALLY
  FreeAndNil(MyIniFile);
 END;
end;

我将许多表单的属性(和其他控件的属性)保存到 INI 文件中。但只有当我尝试保存 fltExtensions.ItemIndex (这是一个 TFilterComboBox)时它才会失败。如果我评论那条线,它就完美了。

知道为什么当我实际关闭应用程序时它会尝试创建一个表单?????????

【问题讨论】:

    标签: delphi


    【解决方案1】:

    读取fltExtensions.ItemIndex属性时出现错误,因为它要求fltExtensions有一个HWND,这要求它的父TFrmDereplic表单有一个HWND,这要求项目的MainForm有一个HWND。但应用处于关闭状态,MainForm 无法再分配其 HWND,因此 TFrmDereplic 在无法为自己获取 HWND 时引发异常。

    将您的 INI 数据保存在表单的 OnDestroy 事件中为时已晚。您需要改为 OnClose 事件。

    【讨论】:

    • 但在某些情况下会跳过 OnClosed。这意味着数据不会保存到磁盘!我目前的实施有效。这是错的吗?我可以按原样使用它吗?我猜想使用 Application 作为该表单的所有者会改变销毁顺序。这让 TFrmDereplic 有机会正确执行 OnDestroy。
    • OnClose 在以大多数方式关闭应用程序时被调用 - 窗口上的 X 按钮、TForm.Close()、Application.Terminate() 等。但是是的,在某些情况下 OnClose 不是总是被调用,但这些条件可以单独处理。您可以将保存代码重构为自己的函数,您可以在需要时从多个位置调用该函数。至于为什么代码本身是错误的,我已经解释过 - OnDestroy 通常来不及访问基于 HWND 的属性值,如 ItemIndex。将 Applicaton 设置为 Owner 只允许 MDI 子级在 MainForm 之前被销毁。
    • “至于为什么代码本身是错误的,我已经解释过了” - 抱歉,我指的是通过将 Application(而不是 MainForm)设置为 Owner 来修复它的新代码。如果这个分配是有效的,我宁愿使用它而不是放弃 OnClose。
    • 确实如此。当 MainForm 的 OnClose 事件被触发时,MDI 子窗体不会触发它们的 OnClose 事件。但是,当 MainForm 的 OnCloseQuery 事件被触发时,它们会触发 OnCloseQuery 事件。
    • 将应用程序指定为所有者允许子窗体在 MainForm 之前而不是之后被销毁,因为 TComponent 以与创建对象相反的顺序销毁其拥有的对象。
    【解决方案2】:

    我查看了一些网站,发现了问题。看起来最好将所有者设置为应用程序,而不是主窗体。 Remy Lebeau 认为真正的问题在于子窗体的 OnDestroy。保存过滤器的窗口没有有效句柄,然后调用 OnDestroy。因此,更改销毁顺序使 TFrmDereplic.OnDestroy 有机会正确执行。

    所以,这里是解决方案:

    解决方案

    FrmDereplic:= TFrmDereplic.Create(Application);

    不要将表单的属性保存在 销毁

    第二个需要几行额外的代码,因为 OnClose 甚至不总是被调用。 这是从 Delphi HELP 中提取的:

    注意:当应用程序关闭时 下来,主窗体收到一个 OnClose 事件,但任何子窗体都不会收到 OnClose 事件。

    如果使用 Application.Terminate,则不会调用 onCloseQuery 和 onClose。 Halt 也一样(但是……这太极端了,对吧?)。

    【讨论】:

      【解决方案3】:

      如果您在问题中提供的代码是真实的,那么我猜错误就在这一行:

       FrmDereplic:= TFrmDereplic.Create(TMainFrm);
      

      我从未尝试过,我不确定编译器是否真的购买它(现在无法测试),但您正试图将一个类设置为 MDI 子窗体的所有者。而不是你应该这样做

      FrmDereplic:= TFrmDereplic.Create(Application);
      

      FrmDereplic:= TFrmDereplic.Create(self);
      

      第一个选项将应用程序设置为 MDI 子窗体的所有者,而第二个选项将 MDI 主窗体的实例设置为所有者。

      希望对您有所帮助。 :-)

      【讨论】:

      • OP 在您发帖前三个小时得出了这个结论。
      • @Vicens - 对不起。它确实是 MainForm 而不是 TMainForm。我在输入代码时输入了错误。在我的代码中,表单具有不同的名称。我将其名称改为 MainForm 以使代码更易于理解(主窗体 = 子窗体的父窗体)。再次抱歉。请注意,Self 将不起作用!它实际上与我的原始(错误)代码等效。为什么?因为 Self = MainForm。
      • @Andreas - 是的,你是对的。我只是想澄清一下,如果提供的代码是真实的,那么错误可能就在该代码行中,另外还提供了将应用程序设置为 MDI 子窗体的所有者的替代方法。当然,也许评论就足够了。 :-)
      • @Altar,我在家里无法访问我的 Delphi 副本,但我可以发誓你应该能够将主窗体设置为 MDI 子窗体的所有者,只要它被声明为 MDI本身。我明天上班试试。
      • @Vicens - 你是对的。将主表单设置为所有者是可以的,但如上所述并不总是有效。使用应用程序作为所有者更好。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-25
      相关资源
      最近更新 更多