gpt4 book ai didi

windows - 如何正确地在任务栏中显示无模式表单

转载 作者:行者123 更新时间:2023-12-03 14:48:44 24 4
gpt4 key购买 nike

我正在努力实现古老的 Delphi 梦想,即在任务栏中显示无模式表单。

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

研究工作

这些是我解决问题的尝试。需要做很多事情才能使其正确运行 - 仅在任务栏上显示一个按钮不是解决方案。让 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 Forces a top-level window onto the taskbar when the window is visible.



它也是一个 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 将按照设计将任何 child 拥有的表单向前推进。这是所有权的全部目的 - 将拥有的表格置于其所有者之上。

使表单实际上无主

解决方案, 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;

顺便说一句,我想调查是否有一种方法可以使用 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;

它应该是向 Delphi 表明您想要形成“没有所有者”的 super secret 方式。但我现在在任何地方都找不到评论。不幸的是, PopupParentPopupMode 的组合不会导致表单实际上是未拥有的:
  • 弹出模式: pmNone
  • 所有者 hwnd:Application.Handle/Application.MainForm.Handle
  • 弹出模式: pmAuto
  • 所有者 hwnd:Screen.ActiveForm.Handle
  • 弹出模式: pmExplicit
  • PopupParent: nil
  • 所有者 hwnd:Application.MainForm.Handle
  • PopupParent:AForm
  • 所有者 hwnd:AForm.Handle
  • PopupParent:自我
  • 所有者 hwnd:Application.MainForm.Handle

  • 我无能为力导致表单实际上没有所有者(每次使用 Spy++ 进行检查)。

    WndParent 期间手动设置 CreateParams :
  • 使表单无主
  • 是否有任务栏按钮
  • 和两个任务栏按钮 行为正确:

  • enter image description here

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

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

    提示窗口

    我的无模式窗口上的一个控件有一个工具顶部:

    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

    在任务栏上显示主窗体

    当我试图创建一个最小的应用程序来重现问题时,我意识到我不能。有一些不同:
  • 在我的 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 而不是 19142101214212141142121411421214114210114212121421212142141214121412141142 checkin spy ++:

    enter image description here

    是的, parent 是 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 时间范围内修复。

    那么正确的解决方法是什么呢?

    奖励阅读
  • 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, 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;

    关于windows - 如何正确地在任务栏中显示无模式表单,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30809532/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com