我试图实现古老的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 – 如何正确使用无模式窗体出现在任务栏中的更多相关文章

  1. wpf – 无边框窗口应用程序比屏幕分辨率占用更多空间

    我在WPF中创建了一个无边界应用程序,它运行得很好.但是,当我将WindowState设置为全屏时,应用程序会占用比屏幕分辨率更多的空间,因此屏幕外的所有方向都有一些像素!任何想法如何防止这种情况发生?谢谢我这样解决了问题:XAML:VisualBasic:它的效果非常好.

  2. 应用程序如何在Windows任务栏上显示对象?

    它被称为“appbars”.您需要使用ShellAPI编写模块,并让用户在Shell中注册它.请注意,如果用户运行的是x64操作系统,则模块也需要为64位,这通常意味着在分发实用程序时,必须同时具有32位和64位版本.Win7有一个更加灵活的界面,名为“taskbarextensions”

  3. 如何获取应用程序的Windows任务栏按钮以显示进度条的进度

    )中引入的功能之一是应用程序的任务栏按钮能够显示该应用程序中进度条的进度.当我创建一个表单并在其上放置一个进度条时,它不会显示在任务栏按钮中,所以我认为这不是自动完成的.如何让Windows7在应用程序的任务栏按钮上显示进度条的进度?

  4. 如何在Windows任务栏中删除多个emacs实例

    如果我使用runemacs.exe启动emacs,任务栏中的窗口将与emacs.exe进程相关联,而不是与runemacs.exe进程相关联.这意味着如果我将runemacs.exe固定,我会在任务栏中显示两个emacs图标.如果我固定emacs.exe,我会得到一个控制台.我怎么吃蛋糕吃呢?

  5. wpf – 在Windows 8.1上使用WindowChrome时,任务栏图标消失

    我在Windows8.1上使用自己的样式WPF窗口时遇到了一些问题.我用WindowChrome编写了一个简单的透明WPF窗口,用于默认的窗口拖动行为:Windows8.1设置:>2台带扩展桌面的显示器>任务栏仅在主桌面上可见摄制:>启动WPF应用程序>在辅助屏幕上移动窗口>最大化辅助屏幕上的窗口>恢复窗口并将其从辅助屏幕拖动到主屏幕–>当鼠标进入主屏幕时,任务栏图标将完全消失!如果再次执行相同的repro,则会再次出现图标.我也尝试使用.NET4.5或.NET4.5.1!

  6. 问:Windows 7任务栏扩展支持的当前状态是什么?

    Windows7AeroTheme有一个全新的任务栏withextensions.Qt中任务栏扩展支持的当前状态是什么?

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

    我试图实现古老的Delphi梦想,在任务栏中出现一个无模式的形式.在任务栏中显示无模式表单的正确方法是什么?

  8. windows-8 – 摆脱Metro界面

    我工作的公司主要运行WindowsXP机器.这些机器变老了,需要更换.我想等到Windows8发布,因为它即将到来.所以,我已经下载了Windows8来测试运行,并找出了我将对用户,程序,goup策略等产生的问题.安装完成后我注意到一切都发生了变化,我有点迷失了一段时间.在我看来,Metro界面很糟糕,肯定会让我的用户感到沮丧.如果他们不习惯使用它们,他们会经常烦扰我.更不用说它会对我们的集团政策

  9. Ubuntu任务栏如何设置为底部

    gsettingssetcom.canonical.Unity.Launcherlauncher-positionBottom

  10. Ubuntu任务栏如何设置为底部?

    从16.04版本开始,Ubuntu提供了一个命令行选项,可以将Launcher启动器移动到屏幕的底部。

随机推荐

  1. static – 在页面之间共享数据的最佳实践

    我想知道在UWP的页面之间发送像’selectedItem’等变量的最佳做法是什么?创建一个每个页面都知道的静态全局变量类是一个好主意吗?

  2. .net – 为Windows窗体控件提供百分比宽度/高度

    WindowsForm开发的新手,但在Web开发方面经验丰富.有没有办法为Windows窗体控件指定百分比宽度/高度,以便在用户调整窗口大小时扩展/缩小?当窗口调整大小时,可以编写代码来改变控件的宽度/高度,但我希望有更好的方法,比如在HTML/CSS中.在那儿?

  3. 使用Windows Azure查询表存储数据

    我需要使用特定帐户吗?>将应用程序部署到Azure服务后,如何查询数据?GoogleAppEngine有一个数据查看器/查询工具,Azure有类似的东西吗?>您可以看到的sqlExpressintance仅在开发结构中,并且一旦您表示没有等效,所以请小心使用它.>您可以尝试使用Linqpad查询表格.看看JamieThomson的thispost.

  4. windows – SetupDiGetClassDevs是否与文档中的设备实例ID一起使用?

    有没有更好的方法可以使用DBT_DEVICEARRIVAL事件中的数据获取设备的更多信息?您似乎必须指定DIGCF_ALLCLASSES标志以查找与给定设备实例ID匹配的所有类,或者指定ClassGuid并使用DIGCF_DEFAULT标志.这对我有用:带输出:

  5. Windows Live ID是OpenID提供商吗?

    不,WindowsLiveID不是OpenID提供商.他们使用专有协议.自从他们的“测试版”期结束以来,他们从未宣布计划继续它.

  6. 如果我在代码中进行了更改,是否需要重新安装Windows服务?

    我写了一个Windows服务并安装它.现在我对代码进行了一些更改并重新构建了解决方案.我还应该重新安装服务吗?不,只需停止它,替换文件,然后重新启动它.

  7. 带有双引号的字符串回显使用Windows批处理输出文件

    我正在尝试使用Windows批处理文件重写配置文件.我循环遍历文件的行并查找我想要用指定的新行替换的行.我有一个’函数’将行写入文件问题是%Text%是一个嵌入双引号的字符串.然后失败了.可能还有其他角色也会导致失败.如何才能使用配置文件中的所有文本?尝试将所有“在文本中替换为^”.^是转义字符,因此“将被视为常规字符你可以尝试以下方法:其他可能导致错误的字符是:

  8. .net – 将控制台应用程序转换为服务?

    我正在寻找不同的优势/劣势,将我们长期使用的控制台应用程序转换为Windows服务.我们为ActiveMQ使用了一个叫做java服务包装器的东西,我相信人们告诉我你可以用它包装任何东西.这并不是说你应该用它包装任何东西;我们遇到了这个问题.控制台应用程序是一个.NET控制台应用程序,默认情况下会将大量信息记录到控制台,尽管这是可配置的.任何推荐?我们应该在VisualStudio中将其重建为服务吗?我使用“-install”/“-uninstall”开关执行此操作.例如,seehere.

  9. windows – 捕获外部程序的STDOUT和STDERR *同时*它正在执行(Ruby)

    哦,我在Windows上:-(实际上,它比我想象的要简单,这看起来很完美:…是的,它适用于Windows!

  10. windows – 当我试图批量打印变量时,为什么我得到“Echo is on”

    我想要执行一个简单的批处理文件脚本:当我在XP中运行时,它给了我预期的输出,但是当我在Vista或Windows7中运行它时,我在尝试打印值时得到“EchoisOn”.以下是程序的输出:摆脱集合表达式中的空格.等号(=)的两侧可以并且应该没有空格BTW:我通常在@echo关闭的情况下启动所有批处理文件,并以@echo结束它们,所以我可以避免将代码与批处理文件的输出混合.它只是使您的批处理文件输出更好,更清洁.

返回
顶部