Ian*_*oyd 8 windows delphi windows-7 windows-95 delphi-xe6
我试图实现古老的德尔福梦想,在任务栏中出现一个无模式的形式.
在任务栏中显示无模式表单的正确方法是什么?
这些是我尝试解决问题的方法.要使其正常运行需要很多东西- 只需在任务栏上显示一个按钮就不是解决方案.让Windows应用程序作为Windows应用程序正常运行应该是我的目标.
对于那些了解我的人,以及我的"展示研究成果"的深度,坚持下去,因为这将是一个疯狂的骑兔子洞.
问题在标题中,以及上面的水平线以上.以下所有内容仅用于说明为什么有些经常重复的建议是不正确的.
最初我有我的"主表格",从中我展示了另一种无模式形式:
procedure TfrmMain.Button2Click(Sender: TObject);
begin
if frmModeless = nil then
Application.CreateForm(TfrmModeless, frmModeless);
frmModeless.Show;
end;
Run Code Online (Sandbox Code Playgroud)
这会正确显示新表单,但任务栏上不会显示任何新按钮:

没有创建任务栏按钮的原因是因为这是设计的.Windows将仅显示"无主"窗口的任务栏按钮.这种无模式Delphi形式绝对拥有.在我的情况下,它由以下所有Application.Handle:

我的项目名称是ModelessFormFail.dpr,Modelessformfail与所有者关联的Windows类名的来源.
幸运的是,有一种方法可以强制 Windows为窗口创建任务栏按钮,即使窗口是拥有的:
WS_EX_APPWINDOWMSDN文档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;
Run Code Online (Sandbox Code Playgroud)
当我们运行这个,新创建的模式窗体不确实得到了自己的任务栏按钮:

我们完成了吗?不,因为它的行为不正确.
如果用户单击frmMain任务栏按钮,则不会提示该窗口.而是提出了另一种形式(frmModeless):

一旦理解了Windows的所有权概念,这就有意义了.根据设计,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;
Run Code Online (Sandbox Code Playgroud)
顺便说一句,我想调查是否有一种方法来使用PopupMode和PopupParent属性使窗口无主.我发誓,我读了评论(从你大卫)上的某个地方有这么一句话,如果你通过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;
Run Code Online (Sandbox Code Playgroud)
它应该是向德尔福表明你想要形成"没有所有者"的超级秘密方式.但我现在无法在任何地方找到评论.不幸的是,没有组合PopupParent和PopupMode导致表单实际上是非拥有的:
Application.Handle/Application.MainForm.HandleScreen.ActiveForm.HandleApplication.MainForm.HandleAForm
AForm.HandleApplication.MainForm.Handle我无能为力可能导致表单实际上没有所有者(每次使用Spy ++检查).
在以下WndParent期间手动设置CreateParams:

我们已经完成了,对吧?我是这么想的.我改变了一切来使用这种新技术.
除了我的修复程序有问题似乎导致其他问题 - Delphi不喜欢我改变为表单的所有权.
我的无模式窗口上的一个控件有一个tooltop:

问题是当这个工具提示窗口出现时,它会导致另一种形式(frmMain,模态)出现.它没有获得激活焦点; 但现在它确实模糊了我所看到的形式:

原因可能是合乎逻辑的.德尔福HintWindow要么通过可能拥有的Application.Handle或Application.MainForm.Handle,而不是它应该为所拥有的形式所拥有:

我会认为这是Delphi的一个错误; 使用错误的所有者.
现在重要的是,我花一点时间来证明我的应用程序不是主要形式和无模式形式:

它实际上是:

即使应用程序布局的实际情况,除提示窗口所有权之外的所有内容都有效.有两个任务栏按钮,点击它们会带来正确的表格:

但我们仍然遇到HintWindow所有权带来错误形式的问题:

当我意识到我无法创建一个最小的应用程序来重现问题时.有一些不同的东西:
在比较了所有内容后,我最终追溯到XE6中的新应用程序MainFormOnTaskbar := True默认添加任何新项目(可能是为了不破坏现有应用程序):
program ModelessFormFail;
//...
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TfrmSacrificialMain, frmSacrificialMain);
//Application.CreateForm(TfrmMain, frmMain);
Application.Run;
end.
Run Code Online (Sandbox Code Playgroud)
当我添加此选项时,工具提示的外观并没有带来错误的表格!:

成功!除此之外,知道即将发生什么的人知道将要发生什么.我的"牺牲"主登录表单显示"真正的"主要表单,隐藏自己:
procedure TfrmSacrificialMain.Button1Click(Sender: TObject);
var
frmMain: TfrmMain;
begin
frmMain := TfrmMain.Create(Application);
Self.Hide;
try
frmMain.ShowModal;
finally
Self.Show;
end;
end;
Run Code Online (Sandbox Code Playgroud)
当这种情况发生时,我"登录",我的任务栏图标完全消失:

这是因为:
现在我们有机会使用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;
Run Code Online (Sandbox Code Playgroud)
我们给它一个旋转:

看起来不错!
除了,当我点击第一个工具栏按钮时,出现错误的表单.它显示模态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
Run Code Online (Sandbox Code Playgroud)
当然,模式窗体的父级未启用.如果是,那就不是一种模态形式.所以VCL回归使用:
WndParent := Application.MainFormHandle;
Run Code Online (Sandbox Code Playgroud)
这意味着我可能必须确保手动(?)设置弹出父母?
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;
Run Code Online (Sandbox Code Playgroud)
除此之外也没有用.单击第一个任务栏按钮会导致错误的表单激活:

此时我完全糊涂了.我的模态形式的父亲应该是frmMain,它是!:

我对可能发生的事情有所了解.
任务栏按钮是frmMain的表示.Windows正在推动这一进程.
当MainFormOnTaskbar设置为false 时,它行为正常.
Delphi VCL中必定有一些魔法可以导致之前的正确性,但是使用MainFormOnTaskbar得到禁用:= True,但它是什么?
我不是第一个希望Delphi应用程序与Windows 95工具栏表现良好的人.我过去曾问过这个问题,但这些答案总是面向Delphi 5,它是旧的中央路由窗口.
我被告知,所有内容都是在Delphi 2007时间框架内修复的.
那么正确的解决方案是什么?
在我看来,根本的问题是,在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.
Run Code Online (Sandbox Code Playgroud)
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.
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
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.
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
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.
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
如果您更喜欢无模式表单由主表单拥有,您可以通过替换TModelessForm.CreateParams为:
procedure TModelessForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
end;
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2254 次 |
| 最近记录: |