我正在努力实现古老的 Delphi 梦想,即在任务栏中显示无模式窗体。
是什么correct如何让无模式窗体出现在任务栏中?
研究工作
这些是我为解决问题所做的尝试。要让它发挥作用需要很多东西正确地- 仅仅在任务栏上显示一个按钮并不是解决方案。我的目标是让 Windows 应用程序像 Windows 应用程序那样正确运行。
对于那些了解我、了解我有多深的人来说“展示研究成果”继续,坚持下去,因为这将是疯狂地掉进兔子洞。
问题就在标题中,也在上面的水平线上方。下面的一切只是为了说明为什么一些经常重复的建议是不正确的。
Windows 仅为无主窗口创建任务栏按钮
最初我有我的「主要表格」,由此我展示了另一种无模式形式:
procedure TfrmMain.Button2Click(Sender: TObject);
begin
if frmModeless = nil then
Application.CreateForm(TfrmModeless, frmModeless);
frmModeless.Show;
end;
这正确显示了新表单,但任务栏上没有出现新按钮:
没有创建任务栏按钮的原因是设计使然。Windows 将仅显示以下窗口的任务栏按钮“无主”。这种无模式的 Delphi 形式绝对是owned。就我而言,它的所有者是Application.Handle
:
我的项目名称是ModelessFormFail.dpr
,这是Windows类名的由来Modelessformfail
与所有者相关。
幸运的是,有一种方法可以forceWindows 为窗口创建任务栏按钮,即使该窗口已拥有:
只需使用WS_EX_APPWINDOW
MSDN 文档WS_EX_APPWINDOW说:
WS_EX_APPWINDOW 0x00040000L
当窗口可见时,强制将顶级窗口置于任务栏上。
它也是一个知名德尔福覆盖技巧CreateParams
并手动添加WS_EX_APPWINDOW
style:
procedure TfrmModeless.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW; //force owned window to appear in taskbar
end;
当我们运行它时,新创建的无模式表单does确实有自己的任务栏按钮:
我们完成了吗?不,因为它的行为不正确。
如果用户点击frmMain任务栏按钮,该窗口不会提前。相反,另一种形式(无模式) 提出:
一旦您理解了 Windows 的概念,这就有意义了所有权。从设计上来说,Windows 可以为任何孩子带来owned形式向前。这就是所有权的全部目的——将拥有的表单保留在其所有者之上。
使表单实际上无主
解决方案,你们中有些人知道不是为了对抗任务栏启发式和窗口。如果我希望表单不被拥有,请将其设置为无主。
这(相当)简单。在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 and PopupParent属性使窗口成为无主窗口。我swear我在某处读到一条评论(来自你大卫),说如果你通过了Self
as the PopupParent
, e.g.:
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
and PopupMode
导致表格actually未被拥有:
- PopupMode: pmNone
- 所有者 hwnd:
Application.Handle/Application.MainForm.Handle
- PopupMode: pmAuto
- 所有者 hwnd:
Screen.ActiveForm.Handle
- PopupMode: pmExplicit
- PopupParent: nil
- 所有者 hwnd:
Application.MainForm.Handle
- PopupParent:
AForm
- PopupParent: Self
- 所有者 hwnd:
Application.MainForm.Handle
我无能为力,无法使表格真正具有no所有者(每次都用 Spy++ 检查)。
设置WndParent
期间手动CreateParams:
-
does使表单无主
- it does有一个任务栏按钮
- 和两个任务栏按钮do行为正确:
我们就完成了,对吧?我是这么想的。我改变了一切以使用这项新技术。
除了我的修复中存在一些问题似乎会导致其他问题 - Delphi 不喜欢我更改表单的所有权。
提示窗口
我的无模式窗口上的控件之一有一个工具提示:
问题是,当出现此工具提示窗口时,它会导致另一种形式(frmMain,模态一)挺身而出。它不会获得激活焦点;但它现在确实掩盖了我正在查看的表格:
原因可能是合乎逻辑的。德尔福提示窗口可能属于Application.Handle
or Application.MainForm.Handle
,而不是被它应该被拥有的形式所拥有:
我认为这是 Delphi 的一个错误;使用错误的所有者。
转移查看实际的应用布局
现在重要的是我花点时间来表明我的应用程序不是主表单和无模式表单:
它实际上是:
- 登录屏幕(隐藏的牺牲主窗体)
- 主屏幕
- 模态控制面板
- 显示无模式形式
即使应用程序布局是现实的,除了提示窗口所有权之外的所有内容都有效。有两个任务栏按钮,单击它们会显示正确的表单:
但我们仍然存在 HintWindow 所有权带来错误形式的问题:
在任务栏上显示主窗体
当我试图创建一个最小的应用程序来重现问题时,我意识到我做不到。有一些不同的东西:
- 我的 Delphi 5 应用程序移植到 XE6 之间
- 在 XE6 中创建的新应用程序
After 比较一切,我最终追溯到XE6中的新应用程序添加了这一事实MainFormOnTaskbar := True
默认情况下,在任何新项目中(大概是为了不破坏现有应用程序):
program ModelessFormFail;
//...
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TfrmSacrificialMain, frmSacrificialMain);
//Application.CreateForm(TfrmMain, frmMain);
Application.Run;
end.
当我添加此选项时,工具提示的外观并没有带来错误的形式!:
成功!除了那些知道即将发生什么的人知道接下来会发生什么. My “牺牲”主登录表单显示“真实”主表单,隐藏自身:
procedure TfrmSacrificialMain.Button1Click(Sender: TObject);
var
frmMain: TfrmMain;
begin
frmMain := TfrmMain.Create(Application);
Self.Hide;
try
frmMain.ShowModal;
finally
Self.Show;
end;
end;
当这种情况发生时,我"login",我的任务栏图标完全消失:
发生这种情况是因为:
- 无主的牺牲主窗体不是不可见的:所以按钮随之而来
- 真正的主要形式是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,而不是当前模态控制面板:
大概是因为新创建的控制面板是 Popup 的父级应用程序.MainForm而不是屏幕.ActiveForm。检查 Spy++:
是的,父母是MainForm.Handle
。事实证明,这是由于 VCL 中的另一个错误造成的。如果表格的PopupMode
is:
VCL尝试使用Application.ActiveFormHandle
as the 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;
但这也不起作用。单击第一个任务栏按钮会导致激活错误的表单:
此时我彻底困惑了。这parent我的模态形式应该是frmMain,它就是!:
所以现在怎么办?
我知道可能会发生什么。
该任务栏按钮代表frmMain。 Windows 正在推动这一点。
除非它在以下情况下表现正确任务栏上的主窗体被设置为 false。
Delphi VCL 中一定有一些魔法导致了之前的正确性,但被禁用了MainFormOnTaskbar := True,但它是什么?
我并不是第一个希望 Delphi 应用程序能够与 Windows 95 工具栏良好配合的人。我过去曾问过这个问题,但这些答案总是针对 Delphi 5 并且它是旧的中央路由窗口。
我被告知所有事情都在 Delphi 2007 年的时间框架内得到修复。
那么正确的解决方案是什么呢?
奖励阅读
- http://blogs.msdn.com/b/oldnewthing/archive/2003/12/29/46371.aspx
- WS_EX_APPWINDOW 是做什么的?
- 调用 TsaveDialog 时,我的详细表单隐藏在主表单后面
- Delphi 博客中的 Oracle:PopupMode 和 PopupParent
- DocWiki:Vcl.Forms.TForm.PopupMode
- DocWiki:Vcl.Forms.TCustomForm.PopupParent
- 如何启动带有隐藏主窗体的Delphi应用程序?