我正在努力实现古老的 Delphi 梦想,即在任务栏中出现无模式表单。
在任务栏中显示无模式表单的正确方法是什么?
研究工作
这些是我解决问题的尝试。需要做很多事情才能使其正常运行- 只是在任务栏上显示一个按钮并不是解决方案。我的目标是让 Windows 应用程序像 Windows 应用程序一样正常运行。
对于那些了解我,以及我的“展示研究工作”有多深入的人,请坚持住,因为这将是疯狂的兔子洞。
问题在标题中,以及上面的水平线上方。以下所有内容仅用于说明为什么一些经常重复的建议是不正确的。
Windows 仅创建为无主窗口的任务栏按钮
最初我有我的“主要形式”,从中我展示了另一种无模式形式:
procedure TfrmMain.Button2Click(Sender: TObject);
begin
if frmModeless = nil then
Application.CreateForm(TfrmModeless, frmModeless);
frmModeless.Show;
end;
这正确显示了新表单,但任务栏上没有出现新按钮:
没有创建任务栏按钮的原因是因为这是设计使然。Windows 只会为“无主”的窗口显示任务栏按钮。这种无模式的 Delphi 形式是绝对拥有的。在我的情况下,它归Application.Handle
:
我的项目名称是,它是与所有者关联ModelessFormFail.dpr
的 Windows 类名的由来。Modelessformfail
幸运的是,有一种方法可以强制Windows 为窗口创建任务栏按钮,即使该窗口是拥有的:
只需使用WS_EX_APPWINDOW
的 MSDN 文档WS_EX_APPWINDOW
说:
WS_EX_APPWINDOW
0x00040000L
当窗口可见时,强制在任务栏上显示一个顶级窗口。
它也是一个著名的 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任务栏按钮,则该窗口不会前移。取而代之的是另一种形式(frmModeless):
一旦您了解了 Windows 的所有权概念,这就是有意义的。Windows 将按照设计将任何儿童拥有的表单向前推进。这是所有权的全部目的 - 将拥有的表单保持在其所有者之上。
使表单实际上是无主的
正如你们中的一些人所知,解决方案不是与任务栏启发式和窗口作斗争。如果我希望表单是无主的,请将其设为无主。
这(相当)简单。CreateParam
强制所有者窗口为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
andPopupParent
属性使窗口无主。我发誓我在某处读到了一条评论(来自大卫),说如果你通过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;
它应该是向 Delphi 表明您希望形成“没有所有者”的超级秘密方式。但我现在在任何地方都找不到评论。不幸的是,没有组合PopupParent
并PopupMode
导致表单实际上是无主的:
- 弹出模式:pmNone
- 业主 hwnd:
Application.Handle/Application.MainForm.Handle
- 业主 hwnd:
- 弹出模式:pmAuto
- 业主 hwnd:
Screen.ActiveForm.Handle
- 业主 hwnd:
- 弹出模式:pmExplicit
- PopupParent:无
- 业主 hwnd:
Application.MainForm.Handle
- 业主 hwnd:
- 弹出父:
AForm
- 业主 hwnd:
AForm.Handle
- 业主 hwnd:
- PopupParent:自我
- 业主 hwnd:
Application.MainForm.Handle
- 业主 hwnd:
- PopupParent:无
我无能为力会导致表单实际上没有所有者(每次使用 Spy++ 检查)。
WndParent
期间手动设置CreateParams
:
- 确实使表单无主
- 它确实有一个任务栏按钮
- 并且两个任务栏按钮的行为都正确:
我们已经完成了,对吧?我是这么想的。我改变了一切以使用这种新技术。
除了我的修复问题似乎会导致其他问题 - Delphi 不喜欢我更改为表单的所有权。
提示窗口
我的无模式窗口上的一个控件有一个工具栏:
问题是当这个工具提示窗口出现时,它会导致另一种形式(frmMain,模态的)出现。它不会获得激活焦点;但它现在确实掩盖了我正在查看的表格:
原因可能是合乎逻辑的。Delphi HintWindow可能由Application.Handle
或拥有Application.MainForm.Handle
,而不是由它应该拥有的表单拥有:
我会认为这是德尔福的一个错误。使用错误的所有者。
转移看实际的应用布局
现在重要的是我要花一点时间来证明我的应用程序不是主窗体和无模式窗体:
其实是:
- 登录屏幕(一个被隐藏的牺牲主窗体)
- 一个主屏幕
- 模态控制面板
- 显示无模式形式
即使在应用程序布局的现实情况下,除了提示窗口所有权之外的一切都有效。有两个任务栏按钮,单击它们会显示正确的表单:
但是我们仍然有 HintWindow 所有权带来错误形式的问题:
ShowMainFormOnTaskbar
当我意识到我做不到时,我正试图创建一个最小的应用程序来重现问题。有一些不同的东西:
- 在我的 Delphi 5 应用程序移植到 XE6 之间
- 在 XE6 中创建的新应用程序
在比较了所有内容之后,我最终将其追溯到 XE6 中的新应用程序MainFormOnTaskbar := True
在任何新项目中默认添加的事实(大概不会破坏现有应用程序):
program ModelessFormFail;
//...
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TfrmSacrificialMain, frmSacrificialMain);
//Application.CreateForm(TfrmMain, frmMain);
Application.Run;
end.
当我添加此选项时,工具提示的出现并没有带来错误的形式!:
成功!除了,知道会发生什么的人知道会发生什么。我的“牺牲”主登录表单显示“真实”主表单,隐藏自己:
procedure TfrmSacrificialMain.Button1Click(Sender: TObject);
var
frmMain: TfrmMain;
begin
frmMain := TfrmMain.Create(Application);
Self.Hide;
try
frmMain.ShowModal;
finally
Self.Show;
end;
end;
当这种情况发生时,我“登录”,我的任务栏图标完全消失:
发生这种情况是因为:
- 未拥有的牺牲主形式不是不可见的:所以按钮随之而来
- 真正的主窗体是拥有的,因此它没有工具栏按钮
使用 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。签入间谍++:
是的,父母是MainForm.Handle
。这原来是因为 VCL 中的另一个错误。如果表格PopupMode
是:
- pmAuto
- pmNone(如果是模态形式)
VCL 尝试Application.ActiveFormHandle
用作hWndParent
. 不幸的是,它会检查模态表单的父级是否已启用:
if (WndParent <> 0) and (
IsIconic(WndParent) or
not IsWindowVisible(WndParent) or
not IsWindowEnabled(WndParent)) then
当然,模态表单的父级未启用。如果是,它就不是模态形式。所以 VCL 回退到使用:
WndParent := Application.MainFormHandle;
手动育儿
这意味着我可能必须确保手动(?)设置弹出育儿?
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;
除了那也没用。单击第一个任务栏按钮会导致激活错误的表单:
在这一点上,我彻底糊涂了。我的模态表单的父级应该是frmMain,它是!:
所以现在怎么办?
我对可能发生的事情有预感。
该任务栏按钮是frmMain的表示。Windows 正在推动这一点。
除非它在MainFormOnTaskbar设置为 false 时表现正确。
Delphi VCL 中一定有一些魔法导致之前的正确性,但被MainFormOnTaskbar := True禁用,但它是什么?
我不是第一个希望 Delphi 应用程序与 Windows 95 工具栏配合得很好的人。我过去曾问过这个问题,但这些答案总是面向 Delphi 5,它是旧的中央路由窗口。
有人告诉我,一切都在 Delphi 2007 时间范围内得到修复。
那么正确的解决方案是什么?