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

Ian*_*oyd 8 windows delphi windows-7 windows-95 delphi-xe6

我试图实现古老的德尔福梦想,在任务栏中出现一个无模式的形式.

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


研究工作

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

MSDN文档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)

顺便说一句,我想调查是否有一种方法来使用PopupModePopupParent属性使窗口无主.我发誓,我读了评论(从你大卫)上的某个地方有这么一句话,如果你通过SelfPopupParent,如:

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)

它应该是向德尔福表明你想要形成"没有所有者"的超级秘密方式.但我现在无法在任何地方找到评论.不幸的是,没有组合PopupParentPopupMode导致表单实际上是非拥有的:

  • 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 ++检查).

在以下WndParent期间手动设置CreateParams:

  • 确实使表格无主
  • 确实有一个任务栏按钮
  • 而这两个任务栏按钮正确的行为:

在此输入图像描述

我们已经完成了,对吧?我是这么想的.我改变了一切来使用这种新技术.

除了我的修复程序有问题似乎导致其他问题 - Delphi不喜欢我改变为表单的所有权.

提示Windows

我的无模式窗口上的一个控件有一个tooltop:

在此输入图像描述

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

在此输入图像描述

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

在此输入图像描述

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

转移看实际的应用程序布局

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

在此输入图像描述

它实际上是:

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

在此输入图像描述

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

在此输入图像描述

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

在此输入图像描述

ShowMainFormOnTaskbar

当我意识到我无法创建一个最小的应用程序来重现问题时.有一些不同的东西:

  • 在我的Delphi 5应用程序之间移植到XE6
  • 在XE6中创建的新应用程序

比较了所有内容后,我最终追溯到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_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;
Run Code Online (Sandbox Code Playgroud)

我们给它一个旋转:

在此输入图像描述

看起来不错!

  • 两个任务栏按钮
  • 工具提示不会向前弹出错误的所有者表单

除了,当我点击第一个工具栏按钮时,出现错误的表单.它显示模态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
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时间框架内修复的.

那么正确的解决方案是什么?

奖金阅读

Dav*_*nan 5

在我看来,根本的问题是,在VCL眼中,你的主要形式不是你的主要形式.一旦你解决了这个问题,所有的问题都会消失.

你应该:

  1. Application.CreateForm对于真正的主要形式,只需调用一次.这是一个很好的规则.考虑Application.CreateForm创建应用程序主要形式的工作.
  2. 创建登录表单并将其设置WndParent0.这确保它出现在任务栏上.然后以模态显示它.
  3. 通过调用以通常的方式创建主窗体Application.CreateForm.
  4. 设定MainFormOnTaskbarTrue.
  5. 设置WndParent0无模式的形式.

就是这样.这是一个完整的例子:

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)