[英]How to correctly have modeless form appear in taskbar
I am trying to achieve the age-old Delphi dream of having a modeless form appear in the taskbar.我正在努力实现古老的 Delphi 梦想,即在任务栏中出现无模式窗体。
What is the correct way to have a modeless form appear in the taskbar?在任务栏中显示无模式窗体的正确方法是什么?
These are my attempts to solve the problem.这些是我解决问题的尝试。 There are a lot of things needed to make it behave correctly - simply having a button appear on the taskbar is not a solution.
需要做很多事情才能使其正常运行——简单地在任务栏上显示一个按钮并不是解决方案。 Having a Windows application behave correctly as a Windows application should is my goal.
让 Windows 应用程序像 Windows 应用程序一样正确运行应该是我的目标。
For those who know me, and how deep my "shows research effort" goes, hang on because it will be wild ride down a rabbit hole.对于那些了解我的人,以及我的“展示研究工作”有多深入,坚持下去,因为它会像兔子洞一样疯狂。
The question is in the title, as well above the horizontal line above.问题在标题中,以及上面的水平线上方。 Everything below only serves to show why some on the oft-repeated suggestions are incorrect.
以下所有内容仅用于说明为什么一些经常重复的建议是不正确的。
Initially i have my "Main Form" , from that i show this other modeless form:最初我有我的“主窗体” ,从中我展示了这个其他无模式窗体:
procedure TfrmMain.Button2Click(Sender: TObject);
begin
if frmModeless = nil then
Application.CreateForm(TfrmModeless, frmModeless);
frmModeless.Show;
end;
This correctly shows the new form, but no new button appears on the taskbar:这正确显示了新表单,但任务栏上没有出现新按钮:
The reason no taskbar button is created is because that is by design.没有创建任务栏按钮的原因是因为这是设计使然。 Windows will only show a taskbar button for a window that "unowned" .
Windows 只会显示“无主”的 window 的任务栏按钮。 This modeless Delphi form is most definitely owned .
这个无模式的 Delphi 表格绝对是拥有的。 In my case it is owned by the
Application.Handle
:在我的例子中,它归
Application.Handle
所有:
My project's name is ModelessFormFail.dpr
, which is the origin of the Windows class name Modelessformfail
associated with the owner.我的项目名称是
ModelessFormFail.dpr
,这是与所有者关联的 Windows class 名称Modelessformfail
的来源。
Fortunately there is a way to force Windows to create a taskbar button for a window, even though the window is owned:幸运的是,有一种方法可以强制Windows 为 window 创建任务栏按钮,即使 window 已被拥有:
WS_EX_APPWINDOW
WS_EX_APPWINDOW
The MSDN documentation of WS_EX_APPWINDOW
says it: WS_EX_APPWINDOW
的 MSDN 文档说:
WS_EX_APPWINDOW
0x00040000L
Forces a top-level window onto the taskbar when the window is visible.WS_EX_APPWINDOW
0x00040000L
当 window 可见时,强制将顶级 window 放到任务栏上。
It also a well-known Delphi trick to override CreateParams
and manually add the WS_EX_APPWINDOW
style:它也是一个著名的 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;
When we run this, the newly created modeless form does indeed get its own taskbar button:当我们运行它时,新创建的无模式窗体确实有自己的任务栏按钮:
And we're done?我们完成了吗? No, because it doesn't behave correctly.
不,因为它的行为不正确。
If the user clicks on the frmMain taskbar button, that window is not brought forward.如果用户单击frmMain任务栏按钮,则不会显示 window。 Instead the other form ( frmModeless ) is brought forward:
相反,提出了另一种形式( frmModeless ):
This makes sense once you understand the Windows concept of ownership .一旦您理解了所有权的 Windows 概念,这是有道理的。 Windows will, by design, bring any child owned forms forward.
Windows 将按照设计将 forms拥有的任何子级向前移动。 It was the entire purpose of ownership - to keep owned forms on top of their owners.
这是所有权的全部目的 - 将拥有的 forms 置于其所有者之上。
The solution, as some of you know is not to fight against the taskbar heuristics and windows. If i want the form to be unowned, make it unowned. 正如你们中的一些人所知,解决方案不是对抗任务栏启发式算法和 windows。如果我希望表单无主,请将其设置为无主。
This is (fairly) simple.这(相当)简单。 In
CreateParam
force the owner windows to be null
:在
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;
As an aside, i wanted to investigate is there was a way to use the PopupMode
and PopupParent
properties to make a window unowned.顺便说一句,我想调查是否有一种方法可以使用
PopupMode
和PopupParent
属性来制作 window unowned。 I swear i read a comment (from you David) somewhere on SO saying that if you passed Self
as the PopupParent
, eg:我发誓我在 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;
it was supposed to be the super-secret way to indicate to Delphi that you want to form to have "no owner" .它应该是向 Delphi 表明您想形成“无所有者”的超级秘密方式。 But i cannot find the comment anywhere on now.
但我现在无法在任何地方找到评论。 Unfortunately, no combination of
PopupParent
and PopupMode
cause a form to actually be un-owned:不幸的是,没有
PopupParent
和PopupMode
的组合会导致表单实际上被取消拥有:
Application.Handle/Application.MainForm.Handle
Application.Handle/Application.MainForm.Handle
Screen.ActiveForm.Handle
Screen.ActiveForm.Handle
Application.MainForm.Handle
Application.MainForm.Handle
AForm
AForm
AForm.Handle
AForm.Handle
Application.MainForm.Handle
Application.MainForm.Handle
Nothing i could do could cause the form to actually have no owner (each time checking with Spy++).我无能为力导致表单实际上没有所有者(每次使用 Spy++ 检查)。
Setting the WndParent
manually during CreateParams
:在
CreateParams
期间手动设置WndParent
:
And we're done, right?我们完成了,对吧? I thought so.
我是这么想的。 I changed everything to use this new technique.
我改变了一切以使用这种新技术。
Except there are problems with my fix that seem to cause other problems - Delphi didn't like me changing to ownership of a form.除了我的修复有问题似乎会导致其他问题 - Delphi 不喜欢我更改为表单的所有权。
One of the controls on my modeless window has a tooltop:我的无模式 window 上的控件之一有一个工具面板:
The problem is that when this tooltip window appears, it causes the other form ( frmMain , the modal one) to come forward.问题是,当此工具提示 window 出现时,它会导致另一种形式( frmMain ,模态形式)出现。 It doesn't gain activation focus;
它不会获得激活焦点; but it does now obscure the form i was look at:
但它现在确实掩盖了我正在查看的表格:
The reason is probably logical.原因可能是合乎逻辑的。 The Delphi HintWindow is probably owned either by
Application.Handle
or Application.MainForm.Handle
, rather than being owned by the form that it should be owned by: Delphi HintWindow可能属于
Application.Handle
或Application.MainForm.Handle
,而不是属于它应该属于的表单:
I would have considered this a bug on Delphi's part;我会认为这是 Delphi 的一个错误; using the wrong owner.
使用错误的所有者。
Now it's important that i take a moment to show that my application isn't a main form and a modeless form:现在重要的是我要花点时间来证明我的应用程序不是主窗体和无模式窗体:
It's actually:它实际上是:
Even with the reality of the application layout, everything except for hint window ownership works.即使在应用程序布局的现实情况下,除了提示 window 所有权之外的所有内容都有效。 There are two taskbar buttons, and clicking them brings the proper form forward:
有两个任务栏按钮,单击它们会显示正确的表单:
But we still have the problem of the HintWindow ownership bringing the wrong form forward:但是我们仍然有 HintWindow 所有权带来错误形式的问题:
It was when i was attempting to create a minimal application to reproduce the problem when i realize i couldn't.当我试图创建一个最小的应用程序来重现问题时,我意识到我做不到。 There was something different:
有一些不同:
After comparing everything, i finally traced it down to the fact that new applications in XE6 add the MainFormOnTaskbar:= True
by default in any new project (presumably to not break existing applications):在比较了所有内容之后,我最终将其追溯到 XE6 中的新应用程序在任何新项目中默认添加
MainFormOnTaskbar:= True
的事实(大概是为了不破坏现有应用程序):
program ModelessFormFail;
//...
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TfrmSacrificialMain, frmSacrificialMain);
//Application.CreateForm(TfrmMain, frmMain);
Application.Run;
end.
When i added this option, then the appearance of the tooltip didn't bring the wrong form forward::当我添加这个选项时,工具提示的出现并没有带来错误的形式::
Success, Except, people who know what's coming know what's coming . Success, Except, 知道未来的人知道未来会发生什么。 My "sacrificial" main login form shows the "real" main form, hiding itself:
我的“牺牲”主登录表单显示了“真正的”主表单,隐藏了自己:
procedure TfrmSacrificialMain.Button1Click(Sender: TObject);
var
frmMain: TfrmMain;
begin
frmMain := TfrmMain.Create(Application);
Self.Hide;
try
frmMain.ShowModal;
finally
Self.Show;
end;
end;
When that happens, and i "login" , my taskbar icon disappers entirely:当发生这种情况时,我“登录” ,我的任务栏图标完全消失了:
This happens because:发生这种情况是因为:
Now we have the opportunity to use WS_EX_APPWINDOW
.现在我们有机会使用
WS_EX_APPWINDOW
了。 I want to force my main form, which is owned, to appear on the taskbar.我想强制拥有的主窗体出现在任务栏上。 So i override
CreateParams
and force it to appear on the taskbar:所以我覆盖了
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;
and we give it a whirl:我们试一试:
Looking pretty good!看起来不错!
except, when i click on the first toolbar button, the wrong form comes up.除了,当我点击第一个工具栏按钮时,错误的表格出现了。 It shows the modal frmMain , rather than the currently modal frmControlPanel :
它显示模态frmMain ,而不是当前模态frmControlPanel :
Presumably because the newly created frmControlPanel was PopupParented to Application.MainForm rather than Screen.ActiveForm .大概是因为新创建的frmControlPanel是 PopupParented 到Application.MainForm而不是Screen.ActiveForm 。 Check in Spy++:
检查间谍++:
Yes, the parent is MainForm.Handle
.是的,父项是
MainForm.Handle
。 This turns out to be because of another bug in the VCL.事实证明这是因为 VCL 中的另一个错误。 If the form's
PopupMode
is:如果表单的
PopupMode
是:
the VCL attempts to use Application.ActiveFormHandle
as the hWndParent
. VCL 尝试使用
Application.ActiveFormHandle
作为hWndParent
。 Unfortunately it then checks if the modal form's parent is enabled:不幸的是,它随后检查模态表单的父级是否已启用:
if (WndParent <> 0) and (
IsIconic(WndParent) or
not IsWindowVisible(WndParent) or
not IsWindowEnabled(WndParent)) then
Of course the modal form's parent is not enabled.当然,模态表单的父级未启用。 If it was, it would not be a modal form.
如果是,它就不是模态形式。 So the VCL falls back to using:
所以 VCL 回退到使用:
WndParent := Application.MainFormHandle;
This means i probably have to be sure to manually(?) set the popup 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;
Except that didn't work either.除此之外也没有用。 Clicking the first taskbar button causes the wrong form to activate:
单击第一个任务栏按钮会导致激活错误的表单:
At this point i'm thoroughly confused.在这一点上,我很困惑。 The parent of my modal form should be frmMain , and it is::
我的模态形式的父级应该是frmMain ,它是:
I have a sense of what might be going on.我知道可能会发生什么。
That taskbar button is a representation of frmMain .该任务栏按钮是frmMain的表示。 Windows is bringing that for forward.
Windows 将其提前。
Except it behaved correctly when MainFormOnTaskbar was set to false.除了当MainFormOnTaskbar设置为 false 时它表现正确。
There must be some magic in Delphi VCL that caused correctness before, but gets disabled with MainFormOnTaskbar:= True , but what is it? Delphi VCL 中一定有一些魔法导致之前正确,但被MainFormOnTaskbar:= True禁用,但它是什么?
I am not the first person to want a Delphi application to behave nicely with the Windows 95 toolbar.我不是第一个希望 Delphi 应用程序能够与 Windows 95 工具栏良好配合的人。 And i've asked this question in the past, but those answers were always geared towards Delphi 5 and it's old central routing window.
我过去问过这个问题,但这些答案总是针对 Delphi 5,它是旧的中央路由 window。
I've been told that everything was fixed around Delphi 2007 timeframe.有人告诉我,一切都在 Delphi 2007 时间范围内修复。
So what is the correct solution?那么正确的解决方法是什么?
It seems to me that the fundamental problem is that your main form is, in the eyes of the VCL, not your main form. 在我看来,根本的问题是,在VCL看来,您的主要形式不是您的主要形式。 Once you fix that, all the problems go away.
解决此问题后,所有问题都会消失。
You should: 你应该:
Application.CreateForm
exactly once, for the real main form. Application.CreateForm
。 That is a good rule to follow. Application.CreateForm
to be to create the main form of your application. Application.CreateForm
的工作是创建应用程序的主要形式。 WndParent
to 0
. WndParent
设置为0
。 That makes sure it appears on the taskbar. Application.CreateForm
. Application.CreateForm
以通常的方式创建主表单。 MainFormOnTaskbar
to be True
. MainFormOnTaskbar
设置为True
。 WndParent
to 0
for the modeless form. WndParent
设置为0
。 And that's it. 就是这样。 Here's a complete example:
这是一个完整的示例:
Project1.dpr 项目1.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 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 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 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 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 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 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
If you'd rather the modeless form was owned by the main form, you can achieve that by replacing TModelessForm.CreateParams
with: 如果您希望无模式表单由主表单拥有,则可以通过将
TModelessForm.CreateParams
替换为:
procedure TModelessForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
end;
Great working thanks so much I use this干得好,非常感谢,我用这个
Application.Initialize;
// Application.MainFormOnTaskbar := True;///*
Application.CreateForm(TAT_musteriler, AT_dATA);
Application.CreateForm(TForm2, Form2);
Application.CreateForm(TForm1, Form1);
Application.CreateForm(TForm3, Form3);
Application.CreateForm(TForm4, Form4);
.... which form is showing (active) that show on windows task bar if * line active only 1 form showing on taskbar when i hide main form and show other form i cant see at windows task bar ....当我隐藏主窗体并显示我在 windows 任务栏上看不到的其他窗体时,在 windows 任务栏上显示(活动)哪个窗体如果 * 行仅在任务栏上显示一个窗体
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.