加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > Windows > 正文

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

发布时间:2020-12-14 04:21:44 所属栏目:Windows 来源:网络整理
导读:我试图实现古老的Delphi梦想,在任务栏中出现一个无模式的形式. 在任务栏中显示无模式表单的正确方法是什么? 研究工作 这些是我尝试解决问题的方法.要使其正常运行需要很多东西 – 只需在任务栏上显示一个按钮就不是解决方案.让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;

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读