首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >pmAuto ModalPopupMode的正确使用或错误解决方法

pmAuto ModalPopupMode的正确使用或错误解决方法
EN

Stack Overflow用户
提问于 2015-08-19 15:02:15
回答 2查看 885关注 0票数 3

我在使用TApplication.ModalPopupMode=pmAuto时遇到了问题,我想知道我的问题是pmAuto的使用还是delphi中的一个bug造成的。

简单用例:

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

行动顺序:

  • Form1创建并显示Form2模式。
  • Form2显示form3模式。
  • 关闭Form3
  • 封闭和自由Form2
  • 显示Form3 <- TComboBox现在包含0项。

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

  • 当ModalPopupMode是pmAuto时,不应该将永久形式和临时形式混为一谈,这是众所周知的事实吗?
  • 如果没有,对这个问题是否有已知的解决办法?
  • 如果它是一个bug,这在最近版本的Delphi中修复了吗?(我正在使用XE4)
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2015-08-19 16:59:38

这并不是一个真正的缺陷,只是不同的窗口在处理情态时如何相互作用的一个怪癖。

第一次创建Form3时,在DFM流期间调用TComboBox.CreateWnd()。当第一次调用Form3.ShowModal()时,如果Form3PopupModepmNone,而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时,它只是隐藏的,它的窗口仍然存在。因此,当Form2Form3关闭时,它们仍然存在并链接在一起,然后当Form2被销毁时,它所有的子窗口和拥有的窗口都被销毁。TComboBox接收一条WM_NCDESTROY消息,将其Handle重置为0,而不通知其其余代码该窗口正在被销毁。因此,TComboBox没有机会保存其当前项,因为没有调用DestroyWnd()。只有当VCL本身正在破坏窗口时,才会调用DestroyWnd(),而不是当操作系统破坏它时。

现在,你怎么能解决这个问题?在释放TComboBox之前,您必须销毁Form2的窗口,触发其DestroyWnd()方法。诀窍是,只有在TComboBox.ControlState属性中启用csRecreating标志时,TComboBox.DestroyWnd()才会保存项目。有几种不同的方法可以做到这一点:

  1. 直接调用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;
  2. 直接打给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()窗口。
  3. 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()
票数 5
EN

Stack Overflow用户

发布于 2018-05-23 23:37:56

基于雷米的出色回答,我在整个应用程序中实现了一些解决这些问题的方法。在我的示例中,您需要从自定义的TForm后代- TMyModalForm中下降所有的模态形式(总之,它始终是一个很好的实践)。我的应用程序中的所有模态形式都是从这个开始的。请注意,在调用继承的方法之前,我还将PopupMode设置为CreateParams()中的pmAuto。这可以防止在显示模态窗口时出现z阶问题,但也会导致问题中描述的窗口句柄问题。此外,我刚刚广播了CM_DESTROYHANDLE如果动作是caHide。这将跳过MDI子窗口和模式窗口的不必要通知,这些窗口在关闭时被销毁。顺便说一句,这个问题在东京的德尔菲10.2.3中仍然存在,供将来参考。

代码语言:javascript
运行
复制
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;
票数 -1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32099167

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档