研究工作
这些是我尝试解决问题的方法.要使其正常运行需要很多东西 – 只需在任务栏上显示一个按钮就不是解决方案.让Windows应用程序作为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
Forces a top-level window onto the taskbar when the window is visible.
它也是一个覆盖CreateParams并手动添加WS_EX_APPWINDOW样式的well-known Delphi技巧:
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不打击任务栏启发式和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
和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;
这应该是向德尔福表明你要形成“没有所有者”的超级秘密方式.但我现在无法在任何地方找到评论.不幸的是,没有PopupParent和PopupMode的组合导致表单实际上是非拥有的:
> PopupMode:pmNone
>所有者hwnd:Application.Handle / Application.MainForm.Handle
> PopupMode:pmAuto
>所有者hwnd:Screen.ActiveForm.Handle
> PopupMode:pmExplicit
> PopupParent:无
>所有者hwnd:Application.MainForm.Handle
> PopupParent:AForm
>所有者hwnd:AForm.Handle
> PopupParent:自我
>所有者hwnd:Application.MainForm.Handle
我无能为力可能导致表单实际上没有所有者(每次检查Spy).
在CreateParams
期间手动设置WndParent:
>确实使表格无主
>它确实有一个任务栏按钮
>并且两个任务栏按钮都正确:
我们完成了,对吧?我是这么想的.我改变了一切来使用这种新技术.
除了我的修复程序有问题似乎导致其他问题 – Delphi不喜欢我改变为表单的所有权.
提示Windows
我的无模式窗口上的一个控件有一个tooltop:
问题是当这个工具提示窗口出现时,它会导致另一种形式(frmMain,模态)出现.它没有获得激活焦点;但它现在确实模糊了我看到的形式:
原因可能是合乎逻辑的. Delphi HintWindow可能由Application.Handle或Application.MainForm.Handle拥有,而不是由它应该拥有的表单所拥有:
转移以查看实际的应用布局
现在重要的是,我花一点时间来证明我的应用程序不是主要形式和非模态形式:
它实际上是:
>登录屏幕(隐藏的牺牲主表格)
>一个主屏幕
>模态控制面板
>显示无模式
即使应用程序布局的实际情况,除提示窗口所有权之外的所有内容都有效.有两个任务栏按钮,点击它们会带来正确的表格:
但我们仍然存在HintWindow所有权带来错误形式的问题:
ShowMainFormOnTaskbar
当我意识到我无法创建一个最小的应用程序来重现问题时.有一些不同的东西:
>我的Delphi 5应用程序之间移植到XE6
>在XE6中创建的新应用程序
在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_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时间框架内修复的.
那么正确的解决方案是什么?
奖金阅读
> http://blogs.msdn.com/b/oldnewthing/archive/2003/12/29/46371.aspx
> What does WS_EX_APPWINDOW do?
> My detail form is hidden behind main form when calling the TsaveDialog
> The Oracle at Delphi Blog: PopupMode and PopupParent
> DocWiki: Vcl.Forms.TForm.PopupMode
> DocWiki: Vcl.Forms.TCustomForm.PopupParent
> How can I start Delphi application with the hidden main form?
你应该:
>对于真正的主窗体,只调用一次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,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,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;