除了ICON_BIG,如何使Delphi 10.2东京荣誉ICON_SMALL(窗口标题栏图标)?

如何在Delphi 10.2东京完成以下目标:我需要Delphi自动设置不只是大图标,而是每个窗口的大小图标。 我需要有机会,对于某些表单,以及对于应用程序,在运行时更改图标。 我希望这可以在不修改VCL.Forms.pas情况下完成(小图标是在窗口标题栏中显示的,从窗口标题左边显示的)。

TCustomForm有一个函数:

 function GetIconHandle: HICON; 

不幸的是,Delphi只设置大图标句柄,例如,这里是VCL.Forms.pas的引用:

  SendMessage(Handle, WM_SETICON, ICON_BIG, GetIconHandle); 

正如你所看到的,上面的代码只设置大图标句柄,但我也需要设置小图标句柄,因为我使用的.ICO文件包含大小图标的不同图像。

让我粗略地总结一下大小图标之间的区别,因为即使是微软的文档也几乎没有提到这一点。 以下是主要区别:

  • 小图标图像显示在窗口标题栏上。

  • 如果任务栏较厚,则大图标图像显示在Windows任务栏(通常位于屏幕的底部); 当您按下Alt + Tab时也会显示大图标图像。

有关大图标和小图标的更多信息,请参阅https://blog.barthe.ph/2009/07/17/wmseticon/ 。

delphi,通过只设置大窗口句柄,有效地淘汰替代图像为窗口标题上显示一个较小的图标。 如果只给出大图标而不是小图标,则Windows将图像从较大的图标重新采样到较小的图标,质量变差,并且更小,更简单的图像的主要想法丢失。

看到三洋的例子图片。 标记为v7.4.16的左列是来自程序的屏幕截图,该程序用设置ICON_BIGICON_SMALL的代码编译。 标记为v7.4.16.22的右栏是来自相同程序的屏幕截图,它没有明确地设置小图标和大图标,而只是将TIcon分配给表单,然后使用其标准代码的Delphi只分配大图标,所以窗口标题栏中的图像由Windows的大图标resize。 您可能会看到由于标准的Delphi行为而导致质量变差。

大vs小图标

在过去,我将VCL.Forms.pas的接口部分中的VCL.Forms.pas从静态变为虚拟,将其从一个function改为VCL.Forms.pas function并添加两个参数:

 procedure GetIconHandle(var Big, Small: HICON); virtual; 

所以VCL.Forms.pas中的后续代码如下所示:

 var Big, Small: HICON; begin [...] GetIconHandle(Big, Small); SendMessage(Handle, WM_SETICON, ICON_BIG, LParam(Big)); SendMessage(Handle, WM_SETICON, ICON_SMALL, LParam(Small)); [...] 

不用修改VCL.Forms.pas就可以轻松完成这个VCL.Forms.pas吗?

我通过修改VCL单元解决了Delphi 2007中的问题,但是我不能再在Delphi 10.20东京版中修改VCL单元,原因如下:

  1. VCL单元编译,但是当我编译我的应用程序时,无论目标是什么目标(Win32 / Win64; Debug / Release),都会得到“内部错误:AV0047C6C7-R000004CC-0”,请参阅https://quality.embarcadero。 com / browse / RSP-18455 – 错误号码(地址)的第一部分是不同的,但第二部分 – R000004CC-0 – 总是相同的。

  2. 我必须手动添加(TObject)到每个不从任何类inheritance的类; 否则我会发现一个错误,即在基类中找不到CreateDestroy 。 在以前的Delphi版本中,编写没有任何祖先的简单class就隐式地从TObjectinheritance了它,但是当我用dcc32 -Q -M -$D- -$M+命令行选项从命令行编译代码时,在基类中找不到CreateDestroy

以下是我过去如何加载图标:

 procedure LoadIconPair(var Big, Small: hIcon; AName: PChar); begin if Win32MajorVersion < 4 then begin Big := LoadIcon(hInstance, AName); Small := 0; end else begin Big := LoadImage(hInstance, AName, IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR); Small := LoadImage(hInstance, AName, IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); end; end; 

这个代码可以进一步改进:32×32和16×16的硬编码大小可以改变,如https://blog.barthe.ph/2009/07/17/wmseticon/所示 , GetSystemMetrics(SM_CXICON)GetSystemMetrics(SM_CYICON)和小图标的GetSystemMetrics(SM_CXSMICON)GetSystemMetrics(SM_CYSMICON)

因此,每个表单本质上称为LoadIconPair ,然后通过覆盖的procedure GetIconHandle(var Big, Small: HICON); override;返回句柄procedure GetIconHandle(var Big, Small: HICON); override; procedure GetIconHandle(var Big, Small: HICON); override;

所以问题如下:

  1. 有没有可能让delphi设置小图标和大图标没有太多的麻烦,没有修改VCL.Forms.pas? (这是主要问题) – 我需要有一些机会,对于某些forms,以及对应用程序,在运行时更改图标。

  2. 如果不是的话,如何在Delphi 10.2的东京版本中为你的应用程序添加一个修改后的源代码VCL单元,在那里单元的接口部分被修改? 有任何指示,或官方指导? 如果有人设法做到这一点,你是怎么做到的? 你是从GUI IDE编译它们的吗? 或者使用命令行dcc32 / dcc64? 还是用msbuild? 否则呢? 你是否还必须手动添加(TObject)到不从任何类inheritance的类,以避免在基类错误中找不到CreateDestroy

更新#1: VCL.Forms.pas设置后,再次设置图标不是一个完整的解决scheme:我们还必须关心应用程序图标,不仅forms图标, 除此之外,VCL.Forms.pas设置图标无论如何,但只有ICON_BIG ,我们必须再次设置图标,这一次设置大小。 你是否有一个想法,我们如何修补VCL.Forms.pas添加设置ICON_SMALL每当它设置大图标,所以我们只修补implementation部分,并将调用一些消息,甚至WM_USER + N请求图标从窗体的句柄,我们的TForm的后裔将实现这个消息处理程序?

更新#2: TApplication和TForm在图标方面有类似的接口,但TApplication是TComponent的后代,它没有窗口句柄,并且没有消息处理程序。 我们可以用TForm做什么,我们不能用TApplication做。

更新#3:我已经实施了一个解决scheme,这是kobik 在他的post中提出的build议和Sertac Akyuz 在他以后的post中提出的build议的混合体 。 也感谢在评论中贡献的其他人。 我已经编译了程序,并把它交给betatesting者,他们已经确认问题已经解决,现在图标看起来不错,TApplication中图标的animation也通过计时器更改图标也正常工作。 谢谢你们!

不允许(理论上)修改Delphi核心VCL / RTL源的interface部分。 事实上,你设法这样做以前返回作为一个飞旋镖现在。 在大多数情况下,您可以在不修补源代码的情况下执行所需的任务,例如通过使用继承,类助手,在运行时修补代码,绕行以及在其他情况下(IMO是最后的手段)修补implementation部分并使用本地副本为你的项目是允许的 – 另请参见How to recompile modifications to VCL source fileHow to change VCL code?

我建议为应用程序中的所有表单(我认为任何大型项目应该这样做)创建一个CreateWnd并覆盖CreateWnd

 procedure TBaseForm.CreateWnd; var Big, Small: HICON; begin inherited; if BorderStyle <> bsDialog then begin GetIconHandles(Big, Small); if Big <> 0 then SendMessage(Handle, WM_SETICON, ICON_BIG, LParam(Big)); if Small <> 0 then SendMessage(Handle, WM_SETICON, ICON_SMALL, LParam(Small)); end; end; 

介绍两种虚拟方法:

 procedure TBaseForm.GetIconResName(var Name: string); begin Name := 'MAINICON'; end; procedure TBaseForm.GetIconHandles(var Big, Small: HICON); var ResName: string; begin Big := 0; Small := 0; GetIconResName(ResName); if ResName = '' then Exit; Big := LoadImage(HInstance, PChar(ResName), IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), 0); Small := LoadImage(HInstance, PChar(ResName), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); end; 

所有你需要在你的子类中做的是重写GetIconResName

即:

 TMyChildForm = class(TBaseForm) protected procedure GetIconResName(var Name: string); override; end; procedure TMyChildForm.GetIconResName(var Name: string); begin Name := 'SPIDERMAN'; end; 

这不是一个完整的解决方案…

我试图给你一些线索来显示修补VCL源码是不需要的。

在任何情况下,如果我使用图标属性(包括应用程序和表单)并提供至少2个大小(16×16和32×32) 32位深度的图标(如果需要,使用其他格式),则不会有任何问题,Windows将显示正确的图标。 即系统在ALT + TAB对话框中显示大图标,在窗口标题中显示小图标。 即使只有ICON_BIG被发送到窗体/应用程序窗口句柄。 (Delphi7的/ Win7的)。 (这就是为什么我要求提供MCVE ,包括关于你的图标格式的信息,而不仅仅是像你这样的代码片段)


由于我对你的确切要求感到困惑,而你仍然拒绝提供MCVE,所以我会尝试提供另一种方法:

你说你也需要处理应用程序图标。 当应用程序被创建时,应用程序图标会被提前设置,因为它们还没有被创建,所以在表单中处理起来并不简单。 但每当Application.Icon改变时, Application.Icon都会通过CM_ICONCHANGED通知表单(参见: procedure TApplication.IconChanged(Sender: TObject); )。 所以你可以通过SendMessage(Application.Handle, WM_SETICON... (这将不会触发CM_ICONCHANGED )或直接设置Application.Icon (这也会触发CM_ICONCHANGED )来重新设置该消息处理程序中的应用程序图标。通过WM_SETICON消息设置大图标和小图标,还需要设置类图标:

 SetClassLong(Application.Handle, GCL_HICON, FIcon); 

所以每当应用程序图标被改变时,在你的表单中被CM_ICONCHANGED覆盖。

 TBaseForm = class(TForm) private procedure CMIconChanged(var Message: TMessage); message CM_ICONCHANGED; ... procedure TBaseForm.CMIconChanged(var Message: TMessage); ... 

如果你需要在应用程序的早期阶段设置这个图标(我认为这是不需要的),只在窗体创建中进行上述操作。

要捕获/处理窗体图标,请在窗体中使用WM_SETICON消息处理程序:

 TBaseForm = class(TForm) private procedure WMSetIcon(var Message: TWMSetIcon); message WM_SETICON; ... procedure TBaseForm.WMSetIcon(var Message: TWMSetIcon); begin if (Message.Icon <> 0) and (BorderStyle <> bsDialog) then begin // this big icon is being set by the framework if Message.BigIcon then begin // FBigIcon := LoadImage/LoadIcon... // if needed set Message.Icon to return a different big icon // Message.Icon := FBigIcon; // in practice create a virtual method to handle this section so your child forms can override it if needed inherited; FSmallIcon := LoadImage/LoadIcon... // set small icon - this will also re-trigger WMSetIcon Perform(WM_SETICON, ICON_SMALL, FSmallIcon); end else inherited; end else inherited; end; 

这应该让你在所有情况下都能被覆盖。

我刚刚创建了一个新的VCL窗体应用程序,将项目选项中的应用程序图标和(唯一的,主)窗体的图标设置为同一个文件,我修改了16×16(32位深度)的外观,以下代码:

 { TForm27 } procedure TForm27.SetIcons; var MainIcon, SmallIcon: HICON; begin MainIcon := Application.Icon.Handle; SmallIcon := Icon.Handle; Perform(WM_SETICON, ICON_BIG, MainIcon); Perform(WM_SETICON, ICON_SMALL, SmallIcon); end; procedure TForm27.FormCreate(Sender: TObject); begin SetIcons; end; 

这产生了以下显示:

修改的表单图标

请注意,两者都使用相同的.ico文件,这是对东京10.2版本的ib.ico的一个难看的修改。 我只是在小图标上画了几条黑线和椭圆。

我不知道你的GetIconHandle修改代码,但你可以在这里做同样的事情。 另外请注意,如果我使用(表单的)Icon.Handle这两个都不起作用。 如果我这样做,丑陋的16×16图标也显示在应用程序栏中。

但是我不需要修改任何VCL代码。 您可以在任何VCL应用程序中执行此操作。

在这个职位的最后,有两个明确的问题。

  1. 有没有可能让德尔福设置小图标和大图标没有太多的麻烦,没有修改VCL.Forms.pas? (这是主要问题);

在这个问题的背景下,这实际上意味着询问是否强制VCL框架本身,而不修改它,加载不同的图标大小是可能的。 这有一个非常简短的答案:不。

如果从字面上看,但是可以使用Delphi来做到这一点。 在你的问题中的代码片段就是证明,这似乎没有什么麻烦。

  1. 如果不是的话,如何在Delphi 10.2的东京版本中为你的应用程序添加一个修改后的源代码VCL单元,在那里单元的接口部分被修改?

这里也是非常简短的答案:不可能,你不能修改Delphi源文件的界面部分。


现在,以上是真正的问题的答案。 这就是它的全部。 但是,尽管这些信息在评论中已经存在,并且已经存在,但是问题仍然存在。 它甚至提供了一个赏金。 我必须很难找到这个问题真的想问什么。

标题中的问题不比帖子中包含的问题更具解释性。

第二个问题有一个更新部分:

VCL.Forms.pas设置后,再次设置图标不是一个完整的解决方案..

这真的没有帮助。 多次阅读更新并思考它; 这个更新与已经被问到的问题,甚至是帖子中给出的背景信息没有关系。 我完全不知道它在说什么。

挖掘更多,我遇到了你的鲁迪的答案的评论:

… Delphi本身有时会设置ICON_BIG,所以在一些复杂的情况下,您将不得不捕捉这些情况,并再次使用ICON_BIG调用WM_SETICON。 …

我会假设问题是这样的。 尽管你设置了合适的图标,在一些不可预见的情况下,VCL会覆盖你的图标。 如果这不是你想要解决的问题,请修改你的问题来提问。

我还没有见过这样的事情,无论如何我们不需要修改“forms.pas”来忽略框架的图标设置。 一个简单的WM_SETICON处理程序就足够了,只是不要让消息被处理。

那么我们如何设置图标呢? 在WM_GETICON的文档中有一个答案:

..没有明确设置图标的窗口(使用WM_SETICON )使用注册的窗口类的图标.. ..

所以,下面是包含在假设问题的表单类中的解决方案:

 type TForm1 = class(TForm) protected procedure WMSetIcon(var Message: TWMSetIcon); message WM_SETICON; procedure CreateParams(var Params: TCreateParams); override; end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.CreateParams(var Params: TCreateParams); begin inherited; Params.WindowClass.hIcon := LoadIcon(HInstance, 'ICON_1'); end; procedure TForm1.WMSetIcon(var Message: TWMSetIcon); begin end; 

下面是在W7上运行的窗体的屏幕截图,小图标( 在这里输入图像说明 )在标题中,大图标( 在这里输入图像说明 )在alt + tab对话框的左上角:

在这里输入图像说明

请注意,XE2的资源管理器有问题,我在“resources and images”对话框中将图标标识为“CANCEL”,但是可执行文件中的图标以“ICON_1”作为标识符,因此我不得不使用它代码。