我正在努力实现德尔福历史悠久的梦想,即在任务栏中出现一个无模式的形式。
在任务栏中显示无模式表单的正确方法是什么?
这些是我尝试解决问题的方法。要使其行为正确 ,需要做很多事情 - 只需在任务栏上显示按钮就不是解决方案。让Windows应用程序作为Windows应用程序正常运行应该是我的目标。
对于那些认识我的人,以及我的"显示研究工作有多深,去了,坚持下去,因为它会在兔子洞里狂奔。
问题出在标题上,以及上面的水平线上方。以下所有内容仅用于说明为什么有些经常重复的建议不正确。
最初我有我的"主表格" ,从中我展示了另一种无模式形式:
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类名Modelessformfail
的来源。
幸运的是,有一种方法可以强制 Windows为窗口创建任务栏按钮,即使窗口是拥有的:
WS_EX_APPWINDOW
WS_EX_APPWINDOW
的MSDN文档说:
WS_EX_APPWINDOW
0x00040000L
当窗口可见时,强制顶层窗口进入任务栏。
还有一个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 任务栏按钮,则不会显示该窗口。而是提出另一种形式( frmModeless ):
一旦您理解了所有权的Windows概念,这就有意义了。通过设计,Windows将带来任何子拥有的表单。这是所有权的全部目的 - 将拥有的表格保留在其所有者之上。
解决方案,as some of you know不是要对抗任务栏启发式和窗口。如果我希望表单是无主的,那就让它无主。
这是(相当)简单。在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
和PopupParent
属性来建立一个无主的窗口。我 发誓 我在SO的某个地方读了一条评论(来自你David)说如果你通过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
的任何组合都不会导致实际的形式为非所有者:
Application.Handle/Application.MainForm.Handle
Screen.ActiveForm.Handle
Application.MainForm.Handle
AForm
AForm.Handle
Application.MainForm.Handle
我无法做的任何事情都可能导致表单实际上拥有 no 所有者(每次使用Spy ++进行检查)。
在CreateParams
期间手动设置WndParent
:
我们已经完成了,对吧?我是这么想的。我改变了一切来使用这种新技术。
除了我的修复程序有问题似乎导致其他问题 - Delphi并不喜欢我改为表单的所有权。
我的无模式窗口上的一个控件有一个tooltop:
问题在于,当出现此工具提示窗口时,它会导致另一种形式( frmMain ,模态一个)出现。它没有获得激活焦点;但现在它确实模糊了我所看到的形式:
原因可能是合乎逻辑的。 Delphi HintWindow 可能由Application.Handle
或Application.MainForm.Handle
拥有,而不是由它应该拥有的表单所有:
我会认为这是Delphi的一个错误;使用错误的所有者。
现在花点时间表明我的应用程序不是主要形式和无模式形式,这一点非常重要:
实际上是:
即使应用程序布局的实际情况,除提示窗口所有权之外的所有内容都有效。有两个任务栏按钮,点击它们会带来正确的表格:
但我们仍然存在HintWindow所有权带来错误形式的问题:
当我意识到我无法创建一个最小的应用程序来重现问题的时候。有一些不同的东西:
在所有内容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。我的"牺牲" 主要登录表单显示了"真实"主要形式,隐藏自己:
procedure TfrmSacrificialMain.Button1Click(Sender: TObject);
var
frmMain: TfrmMain;
begin
frmMain := TfrmMain.Create(Application);
Self.Hide;
try
frmMain.ShowModal;
finally
Self.Show;
end;
end;
当发生这种情况时,我"登录" ,我的任务栏图标完全消失:
这是因为:
现在我们有机会使用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
为:
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时间范围内修复的。
那么正确的解决方案是什么?
答案 0 :(得分:5)
在我看来,根本问题在于你的主要形式是在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;