我在使用TApplication.ModalPopupMode = pmAuto时遇到问题,我想知道我的问题是由于我使用pmAuto还是在delphi中的错误造成的。
简单用例:
行动顺序:
我使用ComboBox作为示例,但我想任何在DestroyWnd过程中保存信息并在CreateWnd过程中恢复它的控件都无法正常工作。我测试了TListBox,它也显示了相同的行为。
答案 0 :(得分:4)
这不是一个真正的错误,只是在处理模态时各种窗口如何相互作用的怪癖。
首次创建Form3
时,在DFM流式传输期间调用TComboBox.CreateWnd()
。首次调用Form3.ShowModal()
时,如果Form3
为RecreateWnd()
且PopupMode
不是{{1},则pmNone
会自行调用Application.ModalPopupMode
}}。好的,所以调用pmNone
,保存项目,然后调用TComboBox.DestroyWnd()
,恢复项目。在TComboBox.CreateWnd()
期间重新创建TComboBox
窗口并不理想,但这次会有效。
第二次调用ShowModal()
时,再次调用Form3.ShowModal()
,不之前调用TComboBox.CreateWnd()
!由于项目尚未保存,因此无法恢复。这就是TComboBox.DestroyWnd()
为空的原因。
但为什么会这样呢?解除TComboBox
后,Form2
的窗口仍与Form3
窗口关联。第一次调用Form2
将Form3.ShowModal
的窗口设置为Form2
的父级/所有者窗口。当你关闭Form3
时,它只是隐藏,它的窗口仍然存在。因此,当TForm
和Form2
关闭时,它们仍然存在并且链接在一起,然后当Form3
被销毁时,其所有子窗口和拥有的窗口都将被销毁。 Form2
收到TComboBox
消息,将其WM_NCDESTROY
重置为0,而不通知其余代码该窗口正在被销毁。因此,Handle
没有机会保存其当前项目,因为未调用TComboBox
。仅当VCL本身正在销毁窗口时才会调用DestroyWnd()
,而不是在操作系统销毁它时调用{。}}。
现在,你怎么解决这个问题?在释放DestroyWnd()
之前,您必须销毁TComboBox
窗口,触发其DestroyWnd()
方法。诀窍是Form2
仅在TComboBox.DestroyWnd()
属性中启用csRecreating
标志时才会保存项目。您可以通过以下几种方式实现这一目标:
直接致电TComboBox.ControlState
和TWinControl.UpdateRecreatingFlag()
。它们都是TWinControl.DestroyHandle()
,因此您可以使用访问者类来访问它们:
protected
直接致电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
中。
向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()
组件收到该邮件时,它会自行调用TWinControl
和UpdateRecreatingFlag()
。
答案 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;