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

时间:2015-06-12 18:04:10

标签: windows delphi windows-7 delphi-xe6 windows-95

我正在努力实现德尔福历史悠久的梦想,即在任务栏中出现一个无模式的形式。

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

研究工作

这些是我尝试解决问题的方法。要使其行为正确 ,需要做很多事情 - 只需在任务栏上显示按钮就不是解决方案。让Windows应用程序作为Windows应用程序正常运行应该是我的目标。

对于那些认识我的人,以及我的"显示研究工作有多深,去了,坚持下去,因为它会在兔子洞里狂奔。

问题出在标题上,以及上面的水平线上方。以下所有内容仅用于说明为什么有些经常重复的建议不正确。

Windows仅为无主窗口创建任务栏按钮

最初我有我的"主表格" ,从中我展示了另一种无模式形式:

procedure TfrmMain.Button2Click(Sender: TObject);
begin
    if frmModeless = nil then
        Application.CreateForm(TfrmModeless, frmModeless);

    frmModeless.Show;
end;

这会正确显示新表单,但任务栏上不会显示任何新按钮:

enter image description here

没有创建任务栏按钮的原因是因为这是设计的。 Windows will only show a taskbar button for a window that "unowned"。这种无模式的Delphi表单绝对是拥有的。就我而言,它归Application.Handle所有:

enter image description here

我的项目名称为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;

当我们运行它时,新创建的无模式窗体确实确实获得了自己的任务栏按钮:

enter image description here

我们已经完成了吗?不,因为它没有正确行为。

如果用户点击 frmMain 任务栏按钮,则不会显示该窗口。而是提出另一种形式( frmModeless ):

enter image description here

一旦您理解了所有权的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;

顺便说一下,我想调查是否有办法使用PopupModePopupParent属性来建立一个无主的窗口。我 发誓 我在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表明你要形成"没有所有者" 。但我现在无法在任何地方找到评论。遗憾的是,PopupParentPopupMode的任何组合都不会导致实际的形式为非所有者:

  • PopupMode: pmNone
    • 所有者hwnd:Application.Handle/Application.MainForm.Handle
  • PopupMode: pmAuto
    • 所有者hwnd:Screen.ActiveForm.Handle
  • PopupMode: pmExplicit
    • PopupParent: nil
      • 所有者hwnd:Application.MainForm.Handle
    • PopupParent:AForm
      • 所有者hwnd:AForm.Handle
    • PopupParent: Self
      • 所有者hwnd:Application.MainForm.Handle

我无法做的任何事情都可能导致表单实际上拥有 no 所有者(每次使用Spy ++进行检查)。

CreateParams期间手动设置WndParent

  • 确实使表单无主
  • 确实有任务栏按钮
  • 并且任务栏按钮执行的行为正确无误:

enter image description here

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

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

提示Windows

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

enter image description here

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

enter image description here

原因可能是合乎逻辑的。 Delphi HintWindow 可能由Application.HandleApplication.MainForm.Handle拥有,而不是由它应该拥有的表单所有:

enter image description here

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

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

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

enter image description here

实际上是:

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

enter image description here

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

enter image description here

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

enter image description here

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.

当我添加此选项时,工具提示的外观并没有带来错误的表格!:

enter image description here

成功!除了知道会发生什么的人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;

当发生这种情况时,我"登录" ,我的任务栏图标完全消失:

enter image description here

这是因为:

  • 非拥有的牺牲主形式不是不可见的:所以按钮随之而来
  • 真正的主要形式是拥有,因此它没有获得工具栏按钮

使用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;

我们给它一个旋转:

enter image description here

看起来很不错!

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

除了,当我点击第一个工具栏按钮时,出现错误的表单。它显示模态 frmMain ,而不是当前的模态 frmControlPanel

enter image description here

大概是因为新创建的 frmControlPanel 是PopupParented到 Application.MainForm 而不是 Screen.ActiveForm 。检查间谍++:

enter image description here

是的,父母是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;

除此之外也没有。单击第一个任务栏按钮会导致错误的表单激活:

enter image description here

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

enter image description here

那么现在呢?

我对可能发生的事情有所了解。

该任务栏按钮代表 frmMain 。 Windows正在推动这一进程。

MainFormOnTaskbar 设置为false时,表现正常。

Delphi VCL中必须有一些神奇的东西才会导致正确性,但是使用 MainFormOnTaskbar:= True 会被禁用,但它是什么?

我不是第一个希望Delphi应用程序与Windows 95工具栏完美配合的人。我过去曾问过这个问题,但这些答案总是针对Delphi 5及其旧的中央路由窗口。

我被告知所有内容都是在Delphi 2007时间范围内修复的。

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

奖金阅读

1 个答案:

答案 0 :(得分:5)

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

你应该:

  1. 对于真正的主要表单,只需调用Application.CreateForm一次。这是一个很好的规则。考虑Application.CreateForm的工作是创建应用程序的主要形式。
  2. 创建登录表单并将其WndParent设置为0。这确保它出现在任务栏上。然后以模态显示它。
  3. 通过调用Application.CreateForm
  4. 以通常的方式创建主表单
  5. MainFormOnTaskbar设为True
  6. 为无模式表单设置WndParent0
  7. 就是这样。这是一个完整的例子:

    <强> 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;