我在使用TApplication.ModalPopupMode=pmAuto时遇到了问题,我想知道我的问题是pmAuto的使用还是delphi中的一个bug造成的。
简单用例:
行动顺序:
我使用ComboBox作为示例,但我认为任何在DestroyWnd过程中保存信息并在CreateWnd过程中恢复信息的控件都不能正常工作。我测试了TListBox,它也显示了相同的行为。
发布于 2015-08-19 16:59:38
这并不是一个真正的缺陷,只是不同的窗口在处理情态时如何相互作用的一个怪癖。
第一次创建Form3
时,在DFM流期间调用TComboBox.CreateWnd()
。当第一次调用Form3.ShowModal()
时,如果Form3
的PopupMode
是pmNone
,而Application.ModalPopupMode
不是pmNone
,那么它本身就会调用RecreateWnd()
。好的,所以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
消息,将其Handle
重置为0,而不通知其其余代码该窗口正在被销毁。因此,TComboBox
没有机会保存其当前项,因为没有调用DestroyWnd()
。只有当VCL本身正在破坏窗口时,才会调用DestroyWnd()
,而不是当操作系统破坏它时。
现在,你怎么能解决这个问题?在释放TComboBox
之前,您必须销毁Form2
的窗口,触发其DestroyWnd()
方法。诀窍是,只有在TComboBox.ControlState
属性中启用csRecreating
标志时,TComboBox.DestroyWnd()
才会保存项目。有几种不同的方法可以做到这一点:
TWinControl.UpdateRecreatingFlag()
和TWinControl.DestroyHandle()
。它们都是protected
,因此可以使用访问器类来访问它们:
输入TComboBoxAccess = class(TComboBox) end;Form2 := TForm2.Create(无);尝试Form2.ShowModal;最后使用TComboBoxAccess(Form3.ComboBox 1)开始UpdateRecreatingFlag(真);DestroyHandle;UpdateRecreatingFlag(假);end;Frm.Free;end;Form3.ShowModal;TWinControl.RecreateWnd()
。它也是protected
,因此您可以使用访问器类来访问它:
类型TComboBoxAccess = class(TComboBox) end;Form2 := TForm2.Create(nil);尝试Form2.ShowModal;最后是TComboBoxAccess(Form3.ComboBox 1).RecreateWnd;Frm.Free;end;Form3.ShowModal;
在下一次需要时,即在后续的TComboBox
中,才会实际创建ShowModal()
窗口。TComboBox
窗口发送一条CM_DESTROYHANDLE
消息,让TWinControl
为您处理所有事情:
Form2 := TForm2.Create(nil);尝试Form2.ShowModal;最后,如果Form3.ComboBox1.HandleAllocation,SendMessage(Form3.ComboBox1.Handle,CM_DESTROYHANDLE,1,0);Frm.Free;end;Form3.ShowModal;
CM_DESTROYHANDLE
在销毁子窗口时由TWinControl.DestroyHandle()
在内部使用。当TWinControl
组件接收到该消息时,它会调用自己的UpdateRecreatingFlag()
和DestroyHandle()
。发布于 2018-05-23 23:37:56
基于雷米的出色回答,我在整个应用程序中实现了一些解决这些问题的方法。在我的示例中,您需要从自定义的TForm后代- TMyModalForm中下降所有的模态形式(总之,它始终是一个很好的实践)。我的应用程序中的所有模态形式都是从这个开始的。请注意,在调用继承的方法之前,我还将PopupMode设置为CreateParams()中的pmAuto。这可以防止在显示模态窗口时出现z阶问题,但也会导致问题中描述的窗口句柄问题。此外,我刚刚广播了CM_DESTROYHANDLE如果动作是caHide。这将跳过MDI子窗口和模式窗口的不必要通知,这些窗口在关闭时被销毁。顺便说一句,这个问题在东京的德尔菲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;
https://stackoverflow.com/questions/32099167
复制相似问题