主窗体在任务栏上加上提示会导致焦点被窃取。

5
我使用Delphi XE2编写了下面的代码。它创建了Form1,并立即创建了一个Form2实例。当我按下Form2上的按钮时,会创建第二个Form2。
现在,如果我将鼠标悬停在第二个最上层的Form2上,并等待工具提示出现,工具提示出现的瞬间,第一个Form2就会置于最前面,窃取焦点。
该问题仅在Application.MainFormOnTaskbarTrue时发生。这还依赖于第一个Form2是从Form1的FormCreate方法创建的。如果我使用PostMessage()来延迟第一个Form2的创建直到应用程序完成初始化,问题就会消失。
我想了解为什么会发生这种情况。我已经了解到Delphi的Application对象处理了许多事情,包括提示显示,并且我知道Delphi可以在初始化期间重新创建窗口句柄,但我还没有能够深入探究以上行为的原因(或者以上两个事实是否相关)。 Project1.dpr
program Project1;

uses
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1},
  Unit2 in 'Unit2.pas' {Form2};

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True; // False makes problem go away
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

Unit1.pas

unit Unit1;
interface
uses
  Vcl.Forms, Unit2;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  public
    procedure CreateForm2;
  end;

var
  Form1: TForm1;

implementation
{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  CreateForm2;
end;

procedure TForm1.CreateForm2;
var
  frm : TForm2;
begin
  frm := TForm2.Create(Application); // (Could pass Self - makes no difference)
  frm.Show;
end;

end.

Unit2.pas

unit Unit2;
interface
uses
  Vcl.Forms, System.Classes, Vcl.Controls, Vcl.StdCtrls, WinApi.Windows;

type
  TForm2 = class(TForm)
    Button1: TButton; // This button has a hint
    procedure Button1Click(Sender: TObject);
  end;

var
  Form2: TForm2;

implementation
uses
  System.SysUtils, Unit1;

{$R *.dfm}

procedure TForm2.Button1Click(Sender: TObject);
begin
  Form1.CreateForm2;
end;

end.
1个回答

7
关键问题在于第一个TForm2实例被创建为应用程序窗口Application.Handle的所有者。这里我指的是Windows中的所有者含义。在VCL语言中,这被称为弹出式父窗口。
现在,当您创建第一个TForm2实例时,Application.MainForm属性仍为nil。因为您没有显式分配PopupParent,所以TCustomForm.CreateParams中的代码将所有者设置为应用程序窗口。
您不希望您的窗口由隐藏的应用程序窗口拥有。这就是为什么第一个TForm2实例有时会出现在所有其他窗口后面,特别是在主窗体后面。它只是使用了错误的所有者创建而已。
Application.Handle拥有的表单将在THintWindow.ActivateHint中显示。这是由读取ParentWindow := Application.Handle一行引起的。然后调用SetWindowPos(Handle, ...),导致错误拥有的窗体出现在前面。可能是因为该窗体也由Application.Handle拥有,所以它出现在前面。现在我没有明确的解释机制,但我并不认为这非常有趣,因为该窗体显然设置错误。
无论如何,根本问题是您创建了一个错误拥有的窗口。因此,解决方案是确保窗口被正确拥有。通过分配PopupParent来实现,例如:
procedure TForm1.CreateForm2;
var
  frm : TForm2;
begin
  frm := TForm2.Create(Application); // (Could pass Self - makes no difference)
  frm.PopupParent := Self;
  frm.Show;
end;

最后,我在TForm2.CreateParams中明确设置了Params.WndParent,而不是PopupParent,因为这样就不可能创建一个没有设置父级的TForm2。我的TForm1实际上是隐藏的(每个TForm2都有自己的任务栏按钮),所以我不确定Form1.Handle是否适当。Raymond Chen说永远不要使用GetDesktopWindow;使用0。在测试中,0、Form1.Handle甚至Application.Handle都可以工作。只是似乎必须让每个TForm2具有相同的所有者。所以我选择了0。 - Ian Goldby
顺便提一下,Peter Below 不同意在此处不使用GetDesktopWindow - Ian Goldby
1
如果主窗体被隐藏了,那么使用0是正确的选择。通过WndParent来控制也是一个明智的举动。至于GetDesktopWindow,Raymond是正确的。Peter Below只是犯了许多人都会犯的错误,就是Raymond所描述的那个错误。 - David Heffernan

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