如何正确地在任务栏中显示无模式窗体
How to correctly have modeless form appear in taskbar
我正在努力实现 age-old Delphi 在任务栏中出现无模式窗体的梦想。
在任务栏中显示无模式窗体的正确方法是什么?
研究工作
这些是我解决问题的尝试。要使它 正确 需要做很多事情 - 简单地在任务栏上显示一个按钮不是解决方案。让 Windows 应用程序像 Windows 应用程序一样正确运行应该是我的目标。
对于那些了解我,以及我的 "shows research effort" 有多深的人,坚持下去,因为它会像兔子洞一样疯狂。
问题在标题中,也在上面的横线上。以下所有内容仅用于说明为什么 oft-repeated 中的某些建议不正确。
Windows 只为无主创建任务栏按钮 windows
最初我有我的"Main Form",从那我展示了另一个无模式形式:
procedure TfrmMain.Button2Click(Sender: TObject);
begin
if frmModeless = nil then
Application.CreateForm(TfrmModeless, frmModeless);
frmModeless.Show;
end;
这正确显示了新表单,但任务栏上没有出现新按钮:
没有创建任务栏按钮的原因是设计使然。 Windows will only show a taskbar button for a window that "unowned"。这种无模式 Delphi 形式绝对是 拥有的 。在我的例子中,它属于 Application.Handle
:
我的项目名称是 ModelessFormFail.dpr
,这是与所有者关联的 Windows class 名称 Modelessformfail
的来源。
幸运的是,有一种方法可以 强制 Windows 为 window 创建任务栏按钮,即使 window 已被拥有:
只需使用WS_EX_APPWINDOW
WS_EX_APPWINDOW
的 MSDN 文档说:
WS_EX_APPWINDOW 0x00040000L
Forces a top-level window onto the taskbar when the window is visible.
它也是一个 well-known Delphi 技巧来覆盖 CreateParams
并手动添加 WS_EX_APPWINDOW
样式:
procedure TfrmModeless.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW; //force owned window to appear in taskbar
end;
当我们运行这个时,新创建的无模式窗体确实确实有自己的任务栏按钮:
我们完成了吗?不,因为它的行为不正确。
如果用户单击 frmMain 任务栏按钮,则不会显示 window。而是提出了另一种形式(frmModeless):
一旦您理解了 所有权 的 Windows 概念,这就很有意义了。 Windows 将按设计将任何 child 拥有的 表格向前移动。这是所有权的全部目的 - 将拥有的表单置于其所有者之上。
使表格真正无主
解决方案,as some of you know 不对抗任务栏启发式和windows。如果我希望表单无主,请将其设置为无主。
这(相当)简单。在 CreateParam
中强制所有者 windows 为 null
:
procedure TfrmModeless.CreateParams(var Params: TCreateParams);
begin
inherited;
//Doesn't work, because the form is still owned
// Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW; //force owned windows to appear in taskbar
//Make the form actually unonwed; it's what we want
Params.WndParent := 0; //unowned. Unowned windows naturally appear on the taskbar.
//There may be a way to simulate this with PopupParent and PopupMode.
end;
顺便说一句,我想调查是否有一种方法可以使用 PopupMode
and PopupParent
属性使 window 成为无主。我 发誓 我在 SO 的某个地方读到一条评论(来自你大卫)说如果你通过 Self
作为 PopupParent
,例如:
procedure TfrmMain.Button1Click(Sender: TObject);
begin
if frmModeless = nil then
begin
Application.CreateForm(TfrmModeless, frmModeless);
frmModeless.PopupParent := frmModeless; //The super-secret way to say "unowned"? I swear David Heffernan mentioned it somewhere on SO, but be damned if i can find it now.
frmModeless.PopupMode := pmExplicit; //happens automatically when you set a PopupParent, but you get the idea
end;
frmModeless.Show;
end;
它应该是 super-secret 向 Delphi 表明你想要形成 "no owner" 的方式。但我现在无法在任何地方找到评论。不幸的是,PopupParent
和 PopupMode
的组合不会导致表单 实际上 成为 un-owned:
- 弹出模式:pmNone
- 所有者hwnd:
Application.Handle/Application.MainForm.Handle
- 弹出模式:pmAuto
- 所有者hwnd:
Screen.ActiveForm.Handle
- 弹出模式:pmExplicit
- 弹出父级:无
- 所有者hwnd:
Application.MainForm.Handle
- 弹出式父对象:
AForm
- 所有者hwnd:
AForm.Handle
- PopupParent:自身
- 所有者hwnd:
Application.MainForm.Handle
我无能为力导致表单实际上 没有 所有者(每次使用 Spy++ 检查)。
在 CreateParams
期间手动设置 WndParent
:
- 是否 使表单无主
- 有任务栏按钮
- 两个任务栏按钮行为正确:
我们完成了,对吧?我是这么想的。我改变了一切以使用这种新技术。
除了我的修复有问题似乎会导致其他问题 - Delphi 不喜欢我更改为表单的所有权。
提示Windows
我的无模式 window 上的一个控件有一个工具栏:
问题是,当这个工具提示 window 出现时,它会导致另一种形式(frmMain,模态形式)出现。它不会获得激活焦点;但它现在确实掩盖了我正在查看的表格:
原因可能是合乎逻辑的。 Delphi HintWindo 可能属于 Application.Handle
或 Application.MainForm.Handle
,而不是属于它应该属于的形式:
我会认为这是 Delphi 方面的错误;使用错误的所有者。
查看实际应用布局的转移
现在重要的是我要花点时间证明我的应用程序不是主窗体和无模式窗体:
实际上是:
- 一个登录屏幕(一个被隐藏的牺牲主窗体)
- 一个主屏幕
- 模态控制面板
- 显示无模式窗体
即使在应用程序布局的现实情况下,除了提示 window 所有权之外的所有内容都有效。有两个任务栏按钮,单击它们会向前移动正确的表单:
但我们仍然遇到 HintWindow 所有权带来错误形式的问题:
ShowMainFormOnTaskbar
当我试图创建一个最小的应用程序来重现问题时,我意识到我做不到。有一些不同:
- 我的Delphi 5应用移植到XE6
- 在 XE6 中创建的新应用程序
在 comparing 一切之后,我终于追查到 XE6 中的新应用程序在任何新项目中默认添加 MainFormOnTaskbar := True
的事实(大概是为了不破坏现有的应用程序):
program ModelessFormFail;
//...
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TfrmSacrificialMain, frmSacrificialMain);
//Application.CreateForm(TfrmMain, frmMain);
Application.Run;
end.
当我添加这个选项时,工具提示的出现并没有将错误的表格向前推进!:
成功!除了,知道会发生什么的人 know what's coming。我的 "sacrificial" 主登录表单显示 "real" 主表单,隐藏自身:
procedure TfrmSacrificialMain.Button1Click(Sender: TObject);
var
frmMain: TfrmMain;
begin
frmMain := TfrmMain.Create(Application);
Self.Hide;
try
frmMain.ShowModal;
finally
Self.Show;
end;
end;
发生这种情况时,我 "login",我的任务栏图标完全消失了:
发生这种情况是因为:
- un-owned 牺牲主窗体不是不可见的:所以按钮也随之而来
- 真正的主窗体是拥有的所以它没有工具栏按钮
使用WS_APP_APPWINDOW
现在我们有机会使用WS_EX_APPWINDOW
。我想强制拥有的主窗体出现在任务栏上。所以我覆盖 CreateParams
并强制它出现在任务栏上:
procedure TfrmMain.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW; //force owned window to appear in taskbar
end;
我们试一试:
看起来不错!
- 两个任务栏按钮
- 工具提示不会向前弹出错误的所有者表单
除了,当我点击第一个工具栏按钮时,错误的表格出现了。它显示模态 frmMain,而不是当前模态 frmControlPanel:
大概是因为新创建的frmControlPanel是PopupParented Application.MainForm而不是Screen.ActiveForm。签入 Spy++:
是的,parent 是 MainForm.Handle
。事实证明这是因为 VCL 中的另一个错误。如果表单的 PopupMode
是:
- pmAuto
- pmNone(如果是模态形式)
VCL 尝试使用 Application.ActiveFormHandle
作为 hWndParent
。不幸的是,它随后检查模态形式的 parent 是否已启用:
if (WndParent <> 0) and (
IsIconic(WndParent) or
not IsWindowVisible(WndParent) or
not IsWindowEnabled(WndParent)) then
当然模态表单的parent是没有启用的。如果是,它就不是模态形式。所以 VCL 退回到使用:
WndParent := Application.MainFormHandle;
手动parenting
这意味着我可能必须确保手动(?)设置弹出窗口 parenting?
procedure TfrmMain.Button2Click(Sender: TObject);
var
frmControlPanel: TfrmControlPanel;
begin
frmControlPanel := TfrmControlPanel.Create(Application);
try
frmControlPanel.PopupParent := Self;
frmControlPanel.PopupMode := pmExplicit; //Automatically set to pmExplicit when you set PopupParent. But you get the idea.
frmControlPanel.ShowModal;
finally
frmControlPanel.Free;
end;
end;
除此之外也没有用。单击第一个任务栏按钮会导致激活错误的表单:
此时我彻底糊涂了。我的模态形式的parent应该是frmMain,结果是!:
那现在呢?
我知道可能会发生什么。
该任务栏按钮代表 frmMain。 Windows 正在推进这一点。
除了当 MainFormOnTaskbar 设置为 false 时它的行为正确。
Delphi VCL 中一定有一些魔法导致了之前的正确性,但是被 MainFormOnTaskbar := True 禁用了,但它是什么?
我不是第一个希望 Delphi 应用程序与 Windows 95 工具栏配合良好的人。我过去问过这个问题,但这些答案总是针对 Delphi 5 并且它是旧的中央路由 window.
我被告知一切都在 Delphi 2007 年左右修复。
那么正确的解决方法是什么?
红利阅读
- http://blogs.msdn.com/b/oldnewthing/archive/2003/12/29/46371.aspx
- What does WS_EX_APPWINDOW do?
- My detail form is hidden behind main form when calling the TsaveDialog
- The Oracle at Delphi Blog: PopupMode and PopupParent
- [=66=
- DocWiki: Vcl.Forms.TCustomForm.PopupParent
- How can I start Delphi application with the hidden main form?
在我看来,根本问题在于,在 VCL 看来,您的主窗体不是您的主窗体。一旦你解决了这个问题,所有的问题都会消失。
你应该:
- 只调用一次
Application.CreateForm
,用于真正的主窗体。这是一个很好的规则。考虑 Application.CreateForm
的工作是创建应用程序的主窗体。
- 创建登录表单并将其
WndParent
设置为 0
。这确保它出现在任务栏上。然后模态显示。
- 通过调用
Application.CreateForm
. 以通常的方式创建主窗体
- 将
MainFormOnTaskbar
设置为 True
。
- 将无模式窗体的
WndParent
设置为 0
。
就是这样。这是一个完整的例子:
Project1.dpr
program Project1;
uses
Vcl.Forms,
uMain in 'uMain.pas' {MainForm},
uLogin in 'uLogin.pas' {LoginForm},
uModeless in 'uModeless.pas' {ModelessForm};
{$R *.res}
begin
Application.Initialize;
Application.ShowHint := True;
Application.MainFormOnTaskbar := True;
with TLoginForm.Create(Application) do begin
ShowModal;
Free;
end;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end.
uLogin.pas
unit uLogin;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs;
type
TLoginForm = class(TForm)
protected
procedure CreateParams(var Params: TCreateParams); override;
end;
implementation
{$R *.dfm}
procedure TLoginForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WndParent := 0;
end;
end.
uLogin.dfm
object LoginForm: TLoginForm
Left = 0
Top = 0
Caption = 'LoginForm'
ClientHeight = 300
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
end
uMain.pas
unit uMain;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, uModeless;
type
TMainForm = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
procedure TMainForm.Button1Click(Sender: TObject);
begin
with TModelessForm.Create(Self) do begin
Show;
end;
end;
end.
uMain.dfm
object MainForm: TMainForm
Left = 0
Top = 0
Caption = 'MainForm'
ClientHeight = 300
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Button1: TButton
Left = 288
Top = 160
Width = 75
Height = 23
Caption = 'Button1'
TabOrder = 0
OnClick = Button1Click
end
end
uModeless.pas
unit uModeless;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TModelessForm = class(TForm)
Label1: TLabel;
protected
procedure CreateParams(var Params: TCreateParams); override;
end;
implementation
{$R *.dfm}
procedure TModelessForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WndParent := 0;
end;
end.
uModeless.dfm
object ModelessForm: TModelessForm
Left = 0
Top = 0
Caption = 'ModelessForm'
ClientHeight = 300
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
ShowHint = True
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 312
Top = 160
Width = 98
Height = 13
Hint = 'This is a hint'
Caption = 'I'#39'm a label with a hint'
end
end
如果您希望无模式窗体归主窗体所有,您可以通过将 TModelessForm.CreateParams
替换为:
来实现
procedure TModelessForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
end;
我正在努力实现 age-old Delphi 在任务栏中出现无模式窗体的梦想。
在任务栏中显示无模式窗体的正确方法是什么?
研究工作
这些是我解决问题的尝试。要使它 正确 需要做很多事情 - 简单地在任务栏上显示一个按钮不是解决方案。让 Windows 应用程序像 Windows 应用程序一样正确运行应该是我的目标。
对于那些了解我,以及我的 "shows research effort" 有多深的人,坚持下去,因为它会像兔子洞一样疯狂。
问题在标题中,也在上面的横线上。以下所有内容仅用于说明为什么 oft-repeated 中的某些建议不正确。
Windows 只为无主创建任务栏按钮 windows
最初我有我的"Main Form",从那我展示了另一个无模式形式:
procedure TfrmMain.Button2Click(Sender: TObject);
begin
if frmModeless = nil then
Application.CreateForm(TfrmModeless, frmModeless);
frmModeless.Show;
end;
这正确显示了新表单,但任务栏上没有出现新按钮:
没有创建任务栏按钮的原因是设计使然。 Windows will only show a taskbar button for a window that "unowned"。这种无模式 Delphi 形式绝对是 拥有的 。在我的例子中,它属于 Application.Handle
:
我的项目名称是 ModelessFormFail.dpr
,这是与所有者关联的 Windows class 名称 Modelessformfail
的来源。
幸运的是,有一种方法可以 强制 Windows 为 window 创建任务栏按钮,即使 window 已被拥有:
只需使用WS_EX_APPWINDOW
WS_EX_APPWINDOW
的 MSDN 文档说:
WS_EX_APPWINDOW
0x00040000L
Forces a top-level window onto the taskbar when the window is visible.
它也是一个 well-known Delphi 技巧来覆盖 CreateParams
并手动添加 WS_EX_APPWINDOW
样式:
procedure TfrmModeless.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW; //force owned window to appear in taskbar
end;
当我们运行这个时,新创建的无模式窗体确实确实有自己的任务栏按钮:
我们完成了吗?不,因为它的行为不正确。
如果用户单击 frmMain 任务栏按钮,则不会显示 window。而是提出了另一种形式(frmModeless):
一旦您理解了 所有权 的 Windows 概念,这就很有意义了。 Windows 将按设计将任何 child 拥有的 表格向前移动。这是所有权的全部目的 - 将拥有的表单置于其所有者之上。
使表格真正无主
解决方案,as some of you know 不对抗任务栏启发式和windows。如果我希望表单无主,请将其设置为无主。
这(相当)简单。在 CreateParam
中强制所有者 windows 为 null
:
procedure TfrmModeless.CreateParams(var Params: TCreateParams);
begin
inherited;
//Doesn't work, because the form is still owned
// Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW; //force owned windows to appear in taskbar
//Make the form actually unonwed; it's what we want
Params.WndParent := 0; //unowned. Unowned windows naturally appear on the taskbar.
//There may be a way to simulate this with PopupParent and PopupMode.
end;
顺便说一句,我想调查是否有一种方法可以使用 PopupMode
and PopupParent
属性使 window 成为无主。我 发誓 我在 SO 的某个地方读到一条评论(来自你大卫)说如果你通过 Self
作为 PopupParent
,例如:
procedure TfrmMain.Button1Click(Sender: TObject);
begin
if frmModeless = nil then
begin
Application.CreateForm(TfrmModeless, frmModeless);
frmModeless.PopupParent := frmModeless; //The super-secret way to say "unowned"? I swear David Heffernan mentioned it somewhere on SO, but be damned if i can find it now.
frmModeless.PopupMode := pmExplicit; //happens automatically when you set a PopupParent, but you get the idea
end;
frmModeless.Show;
end;
它应该是 super-secret 向 Delphi 表明你想要形成 "no owner" 的方式。但我现在无法在任何地方找到评论。不幸的是,PopupParent
和 PopupMode
的组合不会导致表单 实际上 成为 un-owned:
- 弹出模式:pmNone
- 所有者hwnd:
Application.Handle/Application.MainForm.Handle
- 所有者hwnd:
- 弹出模式:pmAuto
- 所有者hwnd:
Screen.ActiveForm.Handle
- 所有者hwnd:
- 弹出模式:pmExplicit
- 弹出父级:无
- 所有者hwnd:
Application.MainForm.Handle
- 所有者hwnd:
- 弹出式父对象:
AForm
- 所有者hwnd:
AForm.Handle
- 所有者hwnd:
- PopupParent:自身
- 所有者hwnd:
Application.MainForm.Handle
- 所有者hwnd:
- 弹出父级:无
我无能为力导致表单实际上 没有 所有者(每次使用 Spy++ 检查)。
在 CreateParams
期间手动设置 WndParent
:
- 是否 使表单无主
- 有任务栏按钮
- 两个任务栏按钮行为正确:
我们完成了,对吧?我是这么想的。我改变了一切以使用这种新技术。
除了我的修复有问题似乎会导致其他问题 - Delphi 不喜欢我更改为表单的所有权。
提示Windows
我的无模式 window 上的一个控件有一个工具栏:
问题是,当这个工具提示 window 出现时,它会导致另一种形式(frmMain,模态形式)出现。它不会获得激活焦点;但它现在确实掩盖了我正在查看的表格:
原因可能是合乎逻辑的。 Delphi HintWindo 可能属于 Application.Handle
或 Application.MainForm.Handle
,而不是属于它应该属于的形式:
我会认为这是 Delphi 方面的错误;使用错误的所有者。
查看实际应用布局的转移
现在重要的是我要花点时间证明我的应用程序不是主窗体和无模式窗体:
实际上是:
- 一个登录屏幕(一个被隐藏的牺牲主窗体)
- 一个主屏幕
- 模态控制面板
- 显示无模式窗体
即使在应用程序布局的现实情况下,除了提示 window 所有权之外的所有内容都有效。有两个任务栏按钮,单击它们会向前移动正确的表单:
但我们仍然遇到 HintWindow 所有权带来错误形式的问题:
ShowMainFormOnTaskbar
当我试图创建一个最小的应用程序来重现问题时,我意识到我做不到。有一些不同:
- 我的Delphi 5应用移植到XE6
- 在 XE6 中创建的新应用程序
在 comparing 一切之后,我终于追查到 XE6 中的新应用程序在任何新项目中默认添加 MainFormOnTaskbar := True
的事实(大概是为了不破坏现有的应用程序):
program ModelessFormFail;
//...
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TfrmSacrificialMain, frmSacrificialMain);
//Application.CreateForm(TfrmMain, frmMain);
Application.Run;
end.
当我添加这个选项时,工具提示的出现并没有将错误的表格向前推进!:
成功!除了,知道会发生什么的人 know what's coming。我的 "sacrificial" 主登录表单显示 "real" 主表单,隐藏自身:
procedure TfrmSacrificialMain.Button1Click(Sender: TObject);
var
frmMain: TfrmMain;
begin
frmMain := TfrmMain.Create(Application);
Self.Hide;
try
frmMain.ShowModal;
finally
Self.Show;
end;
end;
发生这种情况时,我 "login",我的任务栏图标完全消失了:
发生这种情况是因为:
- un-owned 牺牲主窗体不是不可见的:所以按钮也随之而来
- 真正的主窗体是拥有的所以它没有工具栏按钮
使用WS_APP_APPWINDOW
现在我们有机会使用WS_EX_APPWINDOW
。我想强制拥有的主窗体出现在任务栏上。所以我覆盖 CreateParams
并强制它出现在任务栏上:
procedure TfrmMain.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW; //force owned window to appear in taskbar
end;
我们试一试:
看起来不错!
- 两个任务栏按钮
- 工具提示不会向前弹出错误的所有者表单
除了,当我点击第一个工具栏按钮时,错误的表格出现了。它显示模态 frmMain,而不是当前模态 frmControlPanel:
大概是因为新创建的frmControlPanel是PopupParented Application.MainForm而不是Screen.ActiveForm。签入 Spy++:
是的,parent 是 MainForm.Handle
。事实证明这是因为 VCL 中的另一个错误。如果表单的 PopupMode
是:
- pmAuto
- pmNone(如果是模态形式)
VCL 尝试使用 Application.ActiveFormHandle
作为 hWndParent
。不幸的是,它随后检查模态形式的 parent 是否已启用:
if (WndParent <> 0) and (
IsIconic(WndParent) or
not IsWindowVisible(WndParent) or
not IsWindowEnabled(WndParent)) then
当然模态表单的parent是没有启用的。如果是,它就不是模态形式。所以 VCL 退回到使用:
WndParent := Application.MainFormHandle;
手动parenting
这意味着我可能必须确保手动(?)设置弹出窗口 parenting?
procedure TfrmMain.Button2Click(Sender: TObject);
var
frmControlPanel: TfrmControlPanel;
begin
frmControlPanel := TfrmControlPanel.Create(Application);
try
frmControlPanel.PopupParent := Self;
frmControlPanel.PopupMode := pmExplicit; //Automatically set to pmExplicit when you set PopupParent. But you get the idea.
frmControlPanel.ShowModal;
finally
frmControlPanel.Free;
end;
end;
除此之外也没有用。单击第一个任务栏按钮会导致激活错误的表单:
此时我彻底糊涂了。我的模态形式的parent应该是frmMain,结果是!:
那现在呢?
我知道可能会发生什么。
该任务栏按钮代表 frmMain。 Windows 正在推进这一点。
除了当 MainFormOnTaskbar 设置为 false 时它的行为正确。
Delphi VCL 中一定有一些魔法导致了之前的正确性,但是被 MainFormOnTaskbar := True 禁用了,但它是什么?
我不是第一个希望 Delphi 应用程序与 Windows 95 工具栏配合良好的人。我过去问过这个问题,但这些答案总是针对 Delphi 5 并且它是旧的中央路由 window.
我被告知一切都在 Delphi 2007 年左右修复。
那么正确的解决方法是什么?
红利阅读
- http://blogs.msdn.com/b/oldnewthing/archive/2003/12/29/46371.aspx
- What does WS_EX_APPWINDOW do?
- My detail form is hidden behind main form when calling the TsaveDialog
- The Oracle at Delphi Blog: PopupMode and PopupParent
- [=66=
- DocWiki: Vcl.Forms.TCustomForm.PopupParent
- How can I start Delphi application with the hidden main form?
在我看来,根本问题在于,在 VCL 看来,您的主窗体不是您的主窗体。一旦你解决了这个问题,所有的问题都会消失。
你应该:
- 只调用一次
Application.CreateForm
,用于真正的主窗体。这是一个很好的规则。考虑Application.CreateForm
的工作是创建应用程序的主窗体。 - 创建登录表单并将其
WndParent
设置为0
。这确保它出现在任务栏上。然后模态显示。 - 通过调用
Application.CreateForm
. 以通常的方式创建主窗体
- 将
MainFormOnTaskbar
设置为True
。 - 将无模式窗体的
WndParent
设置为0
。
就是这样。这是一个完整的例子:
Project1.dpr
program Project1;
uses
Vcl.Forms,
uMain in 'uMain.pas' {MainForm},
uLogin in 'uLogin.pas' {LoginForm},
uModeless in 'uModeless.pas' {ModelessForm};
{$R *.res}
begin
Application.Initialize;
Application.ShowHint := True;
Application.MainFormOnTaskbar := True;
with TLoginForm.Create(Application) do begin
ShowModal;
Free;
end;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end.
uLogin.pas
unit uLogin;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs;
type
TLoginForm = class(TForm)
protected
procedure CreateParams(var Params: TCreateParams); override;
end;
implementation
{$R *.dfm}
procedure TLoginForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WndParent := 0;
end;
end.
uLogin.dfm
object LoginForm: TLoginForm
Left = 0
Top = 0
Caption = 'LoginForm'
ClientHeight = 300
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
end
uMain.pas
unit uMain;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, uModeless;
type
TMainForm = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
procedure TMainForm.Button1Click(Sender: TObject);
begin
with TModelessForm.Create(Self) do begin
Show;
end;
end;
end.
uMain.dfm
object MainForm: TMainForm
Left = 0
Top = 0
Caption = 'MainForm'
ClientHeight = 300
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Button1: TButton
Left = 288
Top = 160
Width = 75
Height = 23
Caption = 'Button1'
TabOrder = 0
OnClick = Button1Click
end
end
uModeless.pas
unit uModeless;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TModelessForm = class(TForm)
Label1: TLabel;
protected
procedure CreateParams(var Params: TCreateParams); override;
end;
implementation
{$R *.dfm}
procedure TModelessForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WndParent := 0;
end;
end.
uModeless.dfm
object ModelessForm: TModelessForm
Left = 0
Top = 0
Caption = 'ModelessForm'
ClientHeight = 300
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
ShowHint = True
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 312
Top = 160
Width = 98
Height = 13
Hint = 'This is a hint'
Caption = 'I'#39'm a label with a hint'
end
end
如果您希望无模式窗体归主窗体所有,您可以通过将 TModelessForm.CreateParams
替换为:
procedure TModelessForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
end;