4

如何在 Delphi 10.2 Tokyo 中实现以下目标:我需要 Delphi 不仅自动设置大图标,还自动设置每个窗口的大图标和小图标。对于某些表单和 TApplication,我需要有机会在运行时更改图标。我希望无需修改即可完成此操作VCL.Forms.pas(小图标显示在窗口标题栏中,位于窗口标题左侧)。

中有一个功能TCustomForm

function GetIconHandle: HICON;

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

  SendMessage(Handle, WM_SETICON, ICON_BIG, GetIconHandle);

如您所见,上面的代码只设置了大图标句柄,但我还需要设置小图标句柄,因为我使用的 .ICO 文件包含不同的大小图标图像。

让我简要总结一下大图标和小图标之间的区别,因为即使是 Microsoft 文档也几乎没有提及它。以下是主要区别:

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

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

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

Delphi 通过仅设置大窗口句柄,有效地淘汰了替代图像,以在窗口标题上显示较小的图标。如果只给出大图标而不给出小图标,Windows 会将图像从大图标重新采样到小图标,质量会变差,并且会丢失更小、更简单的图像的主要思想。

请参阅由sanyok提供的示例图片。左列,标记为 v7.4.16 是使用代码编译的程序的屏幕截图,该代码设置了ICON_BIGICON_SMALL. 右列,标记为 v7.4.16.22 是来自同一程序的屏幕截图,它没有明确设置小图标和大图标,而只是分配TIcon给一个表单,然后 Delphi 使用其标准代码只分配大图标,因此 Windows 标题栏中的图像由 Windows 从大图标调整大小。您可能会看到由于标准的 Delphi 行为,质量变得多么糟糕。

大图标与小图标

过去,我将接口部分的GetIconHandleVCL.Forms.pas从静态更改为虚拟,将其更改functionprocedure并添加两个参数:

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 单元在 Delphi 2007 中解决了该问题,但由于以下原因,我无法再修改 Delphi 10.20 Tokyo 中的 VCL 单元:

  1. VCL 单元编译,但是,当我编译我的应用程序时,我得到“内部错误:AV0047C6C7-R000004CC-0”,不管目标目标(Win32/Win64;调试/发布),请参阅https://quality.embarcadero。 com/browse/RSP-18455 - 错误号(地址)的第一部分不同,但第二部分 - R000004CC-0 - 始终相同。

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

这是我过去加载图标的方式:

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;

此代码可以进一步改进:硬编码大小的 32x32 和 16x16 可以更改,如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;.

所以问题如下:

  1. 是否可以让 Delphi 轻松设置小图标和大图标而无需修改VCL.Forms.pas?(这是主要问题) - 对于某些表单和 TApplication,我需要有机会在运行时更改图标-时间。;

  2. 如果没有,如何在 Delphi 10.2 Tokyo 下将修改后的源 VCL 单元添加到您的应用程序中,其中修改了单元的接口部分?是否有任何说明或官方指南?如果有人设法做到这一点,你是如何做到的?您是否从 GUI IDE 编译它们?还是使用命令行 dcc32/dcc64?还是使用 msbuild?还是其他?您是否还必须手动将(TObject)添加到不从任何类继承的类中以避免CreateDestroy在基类中找不到错误?

更新#1:在 VCL.Forms.pas 设置后再次设置图标不是一个完整的解决方案:我们还必须注意应用程序图标,而不仅仅是表单图标;除此之外,VCL.Forms.pas 无论如何都会设置图标,但只有ICON_BIG,我们必须再次设置图标,这次设置大小。您是否知道我们如何修补 VCL.Forms.pas 以在设置ICON_SMALL大图标时添加设置,所以我们只修补该implementation部分,并会调用一些消息,甚至 WM_USER+N 来请求图标句柄表单,我们的 TForm 的后代将实现这个消息处理程序?

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

更新#3:我已经实施了一个解决方案,它混合了kobik在他的帖子中提出的建议和Sertac Akyuz在他后来的帖子中提出的建议。还要感谢在评论中做出贡献的其他人。我已经编译了程序并将其提供给了 beta 测试人员,他们已经确认问题已得到解决,图标现在看起来不错,并且通过计时器更改图标在 TApplication 中的图标动画也可以正常工作。谢谢你们!

4

3 回答 3

8

interface不允许(理论上)修补Delphi 的核心 VCL/RTL 源代码部分。您之前设法做到这一点的事实现在以回旋镖的形式返回。在大多数情况下,您可以在不修补源代码的情况下做您需要的事情,例如通过使用继承、类帮助器、在运行时修补代码、绕道,以及在其他情况下(IMO 是最后的手段)修补该implementation部分并使用本地副本你的项目是允许的 - 另见How to recompile modifications to VCL source fileHow to change VCL code?

我建议在应用程序中为所有表单创建一个祖先基类(我认为任何大型项目都应该这样做)并覆盖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.

IE:

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

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

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

我试图给你一些线索来表明不需要修补 VCL 源。

无论如何,如果我使用 Icon 属性(应用程序和表单)并提供至少 2 种尺寸(16x16 和 32x32)32 位深度的图标(如果需要,使用其他格式),我没有任何问题,Windows 将显示正确的图标。即系统在ALT+TAB对话框中显示大图标,在窗口标题中显示小图标。即使只ICON_BIG发送到窗体/应用程序窗口句柄。(德尔福7/Win7)。(这就是我要求MCVE的原因。包括有关您的图标格式的信息。而不仅仅是像您所做的那样的代码片段......)


由于我对您的确切要求感到困惑,并且您仍然拒绝提供 MCVE,因此我将尝试提供另一种方法:

您说您还需要处理应用程序图标。应用程序图标在创建应用程序时提前设置 - 在您的表单中处理并不简单,因为它们尚未创建。但是每当更改Application.Icon时,应用程序都会通知表单(请参阅:) 。因此您可以通过(这不会触发)或直接设置(这也将触发)重新设置该消息处理程序中的应用程序图标。如果需要,通过消息设置大小图标。您还需要设置类图标:CM_ICONCHANGEDprocedure TApplication.IconChanged(Sender: TObject);SendMessage(Application.Handle, WM_SETICON...CM_ICONCHANGEDApplication.IconCM_ICONCHANGEDWM_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;

这应该可以让您在所有情况下都得到覆盖。

于 2017-06-25T18:10:47.603 回答
4

我刚刚创建了一个新的 VCL 表单应用程序,将项目选项中的应用程序图标和(唯一的,主)表单的图标设置为同一个文件,我已经对其进行了修改以使 16x16(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 Tokyo 附带的 ib.ico 的丑陋修改。我只是在小图标上画了几条黑线和椭圆而已。

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

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

于 2017-06-25T17:45:14.243 回答
2

帖子最后有两个明确的问题。

  1. 是否可以让Delphi既不费力又不修改VCL.Forms.pas同时设置小图标和大图标?(这是主要问题);

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

但是,如果从字面上理解,可以使用 Delphi 来执行此操作。您问题中的代码片段就是证明,这似乎一点也不麻烦。

  1. 如果没有,如何在 Delphi 10.2 Tokyo 下将修改后的源 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 的资源管理器有问题,我在“资源和图像”对话框中将图标标识为“取消”,但可执行文件中的图标最终以“ICON_1”作为标识符,所以我不得不在编码。

于 2017-06-30T01:56:03.377 回答