windows – 如何正确使用无模式窗体出现在任务栏中

前端之家收集整理的这篇文章主要介绍了windows – 如何正确使用无模式窗体出现在任务栏中前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我试图实现古老的Delphi梦想,在任务栏中出现一个无模式的形式.

在任务栏中显示无模式表单的正确方法是什么?

研究工作

这些是我尝试解决问题的方法.要使其正常运行需要很多东西 – 只需在任务栏上显示一个按钮就不是解决方案.让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;

顺便说一句,我想调查是否有一种方法可以使用PopupModePopupParent属性来创建一个窗口无主.我发誓我在某处读了一条评论(来自你大卫),说如果你通过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拥有,而不是由它应该拥有的表单所拥有:

我会认为这是Delphi的一个错误;使用错误的所有者.

转移以查看实际的应用布局

现在重要的是,我花一点时间来证明我的应用程序不是主要形式和非模态形式:

它实际上是:

>登录屏幕(隐藏的牺牲主表格)
>一个主屏幕
>模态控制面板
>显示无模式

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

但我们仍然存在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?

在我看来,根本的问题是,在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,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;

猜你在找的Windows相关文章