当调用ShowModal时,窗体被隐藏在其他窗体后面。

31

我的应用程序以模态窗体为基础。主窗体通过ShowModal打开一个窗体,这个窗体再用ShowModal打开另一个窗体,因此我们有一堆叠的模态窗体。有时候会出现这样一个问题:当在新窗体中调用ShowModal时,它会被隐藏在之前的窗体后面,而不是显示在顶部。按下alt+tab键后,窗体会回到顶部,但这并不是一个好的解决方案。您是否遇到过这个问题,您是如何处理的?

编辑:

我使用的是Delphi 7。


感谢您添加版本信息。如果您在问题的文本或标签中包含它,那将会很有帮助。 :-) - Ken White
我先设置了适当的标签,然后添加了EDIT:)。 - LukLed
只要你把它放在某个地方,它就能工作。它不一定非得在标签之一中,只要它在问题的主题或文本中的某个地方即可。这样人们在回答时就知道你有哪些功能可用了。 :-) - Ken White
我们已经遇到了这个第三方应用程序的问题多年了,但是还没有找到解决方法。 - Paul-Sebastian Manole
6个回答

29
您没有说明使用的 Delphi 版本...
较新版本的 Delphi 在 TCustomForm 中添加了两个新属性:PopupMode 和 PopupParent。将您的模态对话框的 PopupParent 设置为创建该对话框的窗体,可以确保子窗体始终位于其父窗体之上。这通常可以解决您描述的问题。
我认为这一对属性是在 Delphi 2006 中添加的,但可能是在 2005 中。它们肯定存在于 Delphi 2007 及以上版本中。
编辑:在看到您正在使用 Delphi 7 后,我唯一的建议是在显示模态窗体的代码中禁用创建它的窗体,并在返回时重新启用它。这应该可以防止创建的窗口接收输入,从而帮助保持 Z 序正确。
下面的代码可能有效(未经测试,因为我不再使用 D7):
procedure TForm1.ShowForm2;
begin
  Self.Enabled := False;
  try
    with TForm2.Create(nil) do
    begin
      try
        if ShowModal = mrOk then
          // Returned OK. Do something;
      finally
        Free;
      end;
    end;
  finally
    Self.Enabled := True;
  end;
end;
如果 Form2 创建了一个模态窗口(正如你提到的那样),只需重复这个过程——禁用 Form2,创建 Form3 并以模态方式显示它,并在其返回时重新启用 Form2。确保像我展示的那样使用 try..finally,这样如果模态窗口中发生错误,创建窗口始终会被重新启用。

1
抱歉,这是Delphi 7。没有PopupMode和PopupParent,但知道它们的存在还是很好的。 - LukLed
我可以尝试这个解决方案,但我们的项目中有很多模态表单,消息框也是模态的,因此有时可能很难实现。但我会尽可能地尝试做到这一点。 - LukLed
使用 Self.Enabled := False; 和 Self.Enabled := True; 对我有效。 - Jordi Corbilla
我可以确认在Delphi XE2中,PopupParent解决方案非常有效,非常感谢! - Duncan
@Duncan:如果我的回答对您有帮助,您可以通过给我点赞来表达感谢。 :-) - Ken White
禁用以前的表单在Delphi 5中没有帮助,但Jim Gilmartin的答案似乎完全正确。问题是由旧版Delphi在TCustomForm.CreateParams()中将WndParent设置为Application.Handle导致的模态窗口。自2004年以来,Borland / Inprise / CodeGear / Embarcadero /无论什么都已经解释了http://blog.therealoracleatdelphi.com/2004/02/popupmode-and-popupparent_10.html - Side S. Fresh

9

非常抱歉另外添加了一个答案,但我进行了更多的研究,发现我的先前答案(DisableProcessWindowsGhosting)并没有帮助。由于我不能总是重现这个问题,所以我不能确定。

我找到了一个看起来比较合适的解决方案。我参考了Delphi 2007中CreateParams方法的代码,它与之相当接近(没有所有处理PopupMode的其他代码)。

我创建了下面的单元,对TForm进行了子类化。

unit uModalForms;

interface

uses Forms, Controls, Windows;
type
  TModalForm = class(TForm)
  protected
    procedure CreateParams(var params: TCreateParams); override;
  end;

implementation

procedure TModalForm.CreateParams(var params: TCreateParams);
begin
  inherited;

  params.WndParent := Screen.ActiveForm.Handle;

  if (params.WndParent <> 0) and (IsIconic(params.WndParent)
    or not IsWindowVisible(params.WndParent)
    or not IsWindowEnabled(params.WndParent)) then
    params.WndParent := 0;

  if params.WndParent = 0 then
    params.WndParent := Application.Handle;
end;

然后我将这个单元与表单单元一起使用,并将表单的类(在.pas代码文件中)从class(TForm)更改为class(TModalForm)

对我有效,似乎接近CodeGear的解决方案。


我先试试 DisableProcessWindowsGhosting,看看是否有效。然后再看这个。谢谢。 - LukLed

2
从这个链接看来,问题出在2000/XP中引入的“Ghosting window”上。您可以通过在启动时调用以下代码来禁用ghosting功能。
procedure DisableProcessWindowsGhosting;
var
  DisableProcessWindowsGhostingProc: procedure;
begin
  DisableProcessWindowsGhostingProc := GetProcAddress(
    GetModuleHandle('user32.dll'),
    'DisableProcessWindowsGhosting');
  if Assigned(DisableProcessWindowsGhostingProc) then
    DisableProcessWindowsGhostingProc;
end; 

我能看到的唯一问题是,这将会影响允许用户最小化、移动或关闭未响应应用程序的主窗口的功能。但这样做可以避免每个调用都需要覆盖Self.Enabled := False代码的情况。

如果它真的能够正常工作,那将是非常好的。谢谢。这个问题困扰了我很长时间。让我感到困惑的是:“但是,如果窗体具有WS_POPUP样式,并且“所有者”是正确的窗口,即使是“虚影”窗体也不允许切换Z顺序到其所有者下面,因此模态对话框突然消失的机会就不存在了。” 我在Forms单元中没有看到任何关于Owner的参考。 - LukLed

1
只需将要打开模态框的窗体的 Visible 属性设置为 False,然后使用 .ShowModal(); 打开它即可。

0

尝试一下 OnShowForm:

PostMessage(Self.Handle, WM_USER_SET_FOCUS_AT_START, 0, 0);

0

我发现在多个窗体上使用“始终置顶”标志会导致Z顺序出现问题。您可能还需要BringWindowToTop函数。

当使用内置的WinAPI(MessageBox)启动消息框时,我发现传递调用窗口的句柄是必要的,以确保提示始终显示在最上面。


1
请看我的回复,Lars D. Owner(或Parent)与所描述的问题无关。该问题是TApplication的隐藏窗口从任务栏中移除后产生的副作用;这导致子窗体失去了它们在Z顺序中的正确位置,而PopupParent和PopupMode就是为了解决这个问题而创建的。 - Ken White
好的,经过进一步检查,我意识到我考虑使用Windows.MessageBox函数,并传递调用窗口的句柄以确保MessageBox显示在调用者的顶部 - 我认为这是“父级”。将编辑我的答案以反映这一点。 - Scott W

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接