pmAuto ModalPopupMode正确使用或bug解决方法

时间:2015-08-19 15:02:16

标签: delphi delphi-xe4

我在使用TApplication.ModalPopupMode = pmAuto时遇到问题,我想知道我的问题是由于我使用pmAuto还是在delphi中的错误造成的。

简单用例:

  • Form1(MainForm)和Form3是永久形式。 (在dpr中创建)
  • Form2是在需要时创建的 之后被释放。
  • Form3包含带有X项的TComboBox。

行动顺序:

  • Form1创建并显示Form2模式。
  • Form2显示form3模态。
  • 关闭Form3
  • 关闭并免费使用Form2
  • 显示Form3< ---- TComboBox现在包含0个项目。

我使用ComboBox作为示例,但我想任何在DestroyWnd过程中保存信息并在CreateWnd过程中恢复它的控件都无法正常工作。我测试了TListBox,它也显示了相同的行为。

  • 当ModalPopupMode是pmAuto时,不应该混合使用永久和临时形式吗?
  • 如果没有,是否有针对此问题的已知解决方法?
  • 如果这是一个错误,这是在更新版本的Delphi中修复的吗? (我正在使用XE4)

2 个答案:

答案 0 :(得分:4)

这不是一个真正的错误,只是在处理模态时各种窗口如何相互作用的怪癖。

首次创建Form3时,在DFM流式传输期间调用TComboBox.CreateWnd()。首次调用Form3.ShowModal()时,如果Form3RecreateWnd()PopupMode不是{{1},则pmNone会自行调用Application.ModalPopupMode }}。好的,所以调用pmNone,保存项目,然后调用TComboBox.DestroyWnd(),恢复项目。在TComboBox.CreateWnd()期间重新创建TComboBox窗口并不理想,但这次会有效。

第二次调用ShowModal()时,再次调用Form3.ShowModal() ,不之前调用TComboBox.CreateWnd()!由于项目尚未保存,因此无法恢复。这就是TComboBox.DestroyWnd()为空的原因。

但为什么会这样呢?解除TComboBox后,Form2的窗口仍与Form3窗口关联。第一次调用Form2Form3.ShowModal的窗口设置为Form2的父级/所有者窗口。当你关闭Form3时,它只是隐藏,它的窗口仍然存在。因此,当TFormForm2关闭时,它们仍然存在并且链接在一起,然后当Form3被销毁时,其所有子窗口和拥有的窗口都将被销毁。 Form2收到TComboBox消息,将其WM_NCDESTROY重置为0,而不通知其余代码该窗口正在被销毁。因此,Handle没有机会保存其当前项目,因为未调用TComboBox。仅当VCL本身正在销毁窗口时才会调用DestroyWnd(),而不是在操作系统销毁它时调用{。}}。

现在,你怎么解决这个问题?在释放DestroyWnd()之前,您必须销毁TComboBox窗口,触发其DestroyWnd()方法。诀窍是Form2仅在TComboBox.DestroyWnd()属性中启用csRecreating标志时才会保存项目。您可以通过以下几种方式实现这一目标:

  1. 直接致电TComboBox.ControlStateTWinControl.UpdateRecreatingFlag()。它们都是TWinControl.DestroyHandle(),因此您可以使用访问者类来访问它们:

    protected
  2. 直接致电type TComboBoxAccess = class(TComboBox) end; Form2 := TForm2.Create(nil); try Form2.ShowModal; finally with TComboBoxAccess(Form3.ComboBox1) do begin UpdateRecreatingFlag(True); DestroyHandle; UpdateRecreatingFlag(False); end; Frm.Free; end; Form3.ShowModal; 。它也是TWinControl.RecreateWnd(),因此您可以使用访问者类来访问它:

    protected

    type TComboBoxAccess = class(TComboBox) end; Form2 := TForm2.Create(nil); try Form2.ShowModal; finally TComboBoxAccess(Form3.ComboBox1).RecreateWnd; Frm.Free; end; Form3.ShowModal; 窗口实际上不会在下次需要时创建,在随后的TComboBox中。

  3. ShowModal()窗口发送TComboBox消息,让CM_DESTROYHANDLE为您处理所有内容:

    TWinControl
    在销毁子窗口时,Form2 := TForm2.Create(nil); try Form2.ShowModal; finally if Form3.ComboBox1.HandleAllocated then SendMessage(Form3.ComboBox1.Handle, CM_DESTROYHANDLE, 1, 0); Frm.Free; end; Form3.ShowModal; 在内部使用

    CM_DESTROYHANDLE。当TWinControl.DestroyHandle()组件收到该邮件时,它会自行调用TWinControlUpdateRecreatingFlag()

答案 1 :(得分:0)

根据Remy的优秀答案,我实施了一些修复整个应用程序中的问题的方法。您将需要从我的示例中的自定义TForm后代 - TMyModalForm下降所有模态形式(无论如何,IMO始终是一个好习惯)。我的应用程序中的所有模态形式都源于此。请注意,在调用继承方法之前,我还在CreateParams()中将PopupMode设置为pmAuto。这可以在显示模态窗口时防止z顺序问题,但也会导致问题中描述的窗口句柄问题。另外,如果动作是caHide,我只播放CM_DESTROYHANDLE。这会跳过MDI子窗口和模态窗口的不必要通知,这些窗口会在关闭时被销毁。 顺便说一句,为了将来参考,Delphi 10.2.3东京仍然存在这个问题。

type
  TMyModalForm = class(TForm)
  protected
    procedure DoClose(var Action: TCloseAction); override;
    procedure CreateParams(var Params: TCreateParams); override;
  end;

procedure TMyModalForm.DoClose(var Action: TCloseAction);
var
  Msg: TMessage;
begin
  inherited DoClose(Action);
  if Action = caHide then
  begin
    FillChar(Msg, SizeOf(Msg), 0);
    Msg.Msg := CM_DESTROYHANDLE;
    Msg.WParam := 1;
    Broadcast(Msg);
  end;
end;

procedure TMyModalForm.CreateParams(var Params: TCreateParams);
begin
  PopupMode := pmAuto;
  inherited;
end;

end;