How to make Delphi 10.2 Tokyo honor ICON_SMALL (window title icon) in addition to ICON_BIG?

How to achieve the next goal in Delphi 10.2 Tokyo: I need Delphi to automatically set not only the large icon, but also the large and small icons for each window. I need to be able for some forms and for TApplication to change icons at runtime. I want this to be done unchanged VCL.Forms.pas

(the little icon is the one that appears in the window title bar, to the left of the window title).

There TCustomForm

is a function:

function GetIconHandle: HICON;

      

Unfortunately Delphi only sets a large icon handle, for example, here's a quote from VCL.Forms.pas

:

  SendMessage(Handle, WM_SETICON, ICON_BIG, GetIconHandle);

      

As you can see, the above code only sets the large icon handle, but I also need to set the small icon handle as the .ICO files used contain different images for the big and small icons.

Let me briefly describe the differences between large and small icons, as even Microsoft's documentation says almost nothing about it. Here are the main differences:

  • A small icon image is displayed in the title bar of the window.

  • A large icon is displayed on the Windows taskbar (usually at the bottom of the screen) if the taskbar is thick; the large icon image is also displayed when you press Alt + Tab.

See https://blog.barthe.ph/2009/07/17/wmseticon/ for more information on large and small icons .

Delphi, by setting only a large window handle, effectively disables the alternate image for the smaller icon displayed in the window titles. If only a large icon is specified, but not a small one, Windows will cancel the image from the larger icon to the smaller one, the quality will deteriorate, and the basic idea of ​​the smaller, simpler image will be lost.

See example image courtesy of sanyok. The left column labeled v7.4.16, is a screenshot of the program compiled with the code that sets both ICON_BIG

and ICON_SMALL

. The right column labeled v7.4.16.22 is a screenshot from the same program which does not explicitly set both small and large icons, but simply assigns to the form to the TIcon

form, and then Delphi, using its standard code, simply assigns only the large icon. so the image from the windows title bar is resized with a large icon. You can see how poor quality results from standard Delphi behavior.

Big vs Small Icons

In the past, I've changed the GetIconHandle in the interface section VCL.Forms.pas

from static to virtual, changing it from function

to procedure

and adding two parameters:

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

      

So the following code in VCL.Forms.pas was as follows:

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

      

Is it easy to accomplish this without changing VCL.Forms.pas

?

I solved the problem in Delphi 2007 by changing VCL units, but I can no longer change VCL units in Delphi 10.20 Tokyo for the following reasons:

  • VCL compilations are compiled, but then when I compile my application I get "Internal error: AV0047C6C7-R000004CC-0" regardless of target targets (Win32 / Win64; Debug / Release), see https: //quality.embarcadero. com / browse / RSP-18455 - the first part of the error number (address) is different, and the second - R000004CC-0 - is always the same.

  • I need to manually add (TObject) to each of the classes that do not inherit from any class; otherwise I am reporting an error that Create

    or Destroy

    not found in the base class. In previous versions of Delphi, writing simple class

    without any ancestor implicitly inherited it from TObject

    , but when I compile the code from the command line dcc32

    with command line parameters dcc32 -Q -M -$D- -$M+

    , this error occurs when Create

    or is Destroy

    not found in the base class.

This is how I loaded icons in the past:

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;

      

This code can be further improved: hardcoded dimensions 32x32 and 16x16 can be modified as suggested in https://blog.barthe.ph/2009/07/17/wmseticon/ , in GetSystemMetrics(SM_CXICON)

, GetSystemMetrics(SM_CYICON)

for large and icons GetSystemMetrics(SM_CXSMICON)

and GetSystemMetrics(SM_CYSMICON)

small icons.

So, each form is essentially called LoadIconPair

and then returns the descriptors via the overwritten one procedure GetIconHandle(var Big, Small: HICON); override;

.

So the questions are:

  • Is it possible to let Delphi set both small and large icons without much hassle and without changingVCL.Forms.pas?

    (this is the main question) - I need to be able, for some forms and for TApplication, to change icons at runtime.

  • If not, how to add the modified VCL source to your application in Delphi 10.2 Tokyo where the device interface section changes? Are there any instructions or official guidelines? If someone succeeded, how did you do it? Have you compiled them from the GUI IDE? Or using the dcc32 / dcc64 command line? Or with msbuild? Or otherwise? Do you also need to manually add (TObject) classes that do not inherit from any class to avoid Create

    or Destroy

    not found in the base class error?

Update # 1: Installing icons again after installing VCL.Forms.pas is not a complete solution: we also have to take care of the application icon, not just the form icons; also, VCL.Forms.pas installs icons anyway, but only ICON_BIG

, we have to install icons again, this time setting both small and large. Do you have an idea on how we can fix the VCL.Forms.pas to add a parameter ICON_SMALL

whenever it sets a large icon, so we are only fixing the section implementation

and will be calling some messages, even WM_USER + N to request icon handles from the form. and will our TForm descendant execute this message handler?

Update # 2: TApplication and TForm have similar interfaces with regard to icons, but TApplication is a descendant of TComponent which has no window handle and thus no message handlers. What we can do with TForm, we cannot do with TApplication.

Update # 3: I have implemented a solution that is a mixture of what kobik has suggested in his post and Sertac Akyuz suggested in his later post . Thanks also to those who contributed to the comments. I compiled the program and gave it to the beta testers and they confirmed that the problem was fixed, the icon now looks good, and the animation of the icon in TApplication by changing the icons by timer also works correctly. Thanks everyone!

+3


source to share


3 answers


Setting the interface

VCL / RTL sources section of the Delphi core is not valid (in theory). The fact that you managed to do this has previously returned like a boomerang. You can do what you need most of the time without fixing the sources, for example. Using inheritance, class mates, fix the code at runtime, workarounds, and in other cases (in which the IMO is the last resort) amend section implementation

and use a local copy of your project that is allowed - see also. How to recompile modifications to VCL source file

andHow to change VCL code?

I suggest creating a base ancestor class for all of your forms (I think any large project should do this) in the application and override 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;

      

Introduce two virtual methods:

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;

      

All you need to do in your child classes is override GetIconResName

.

i.e:

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

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

      


This is not a complete solution ...

I tried to give you some hints to show that a VCL source fix is ​​not required.



Anyway, I have no problem, if I use the Icon property (both application and form) and provide an icon with a size of at least 2 (16x16 and 32x32) 32-bit depth (use other formats if necessary), Windows will display the correct one icon. that is, the system displays a large icon in the ALT + TAB dialog box and a small icon in the title bar. although only the Form / Application window handle is sent ICON_BIG

. (Delphi7 / Win7). (This is why I asked for MCVE , including information about your Icons format, not just code snippets as you did ...)


Since I am confused about your exact requirements and you still refuse to provide MCVE, I will try to suggest a different approach:

You say you need to handle the app icon as well. the app icon is set before when the app is created - not easy to handle in your forms because they haven't been created yet. but when changed, the Application.Icon

application notifies the forms with CM_ICONCHANGED

(see procedure TApplication.IconChanged(Sender: TObject);

). so you can re-install the app icon in that message handler via SendMessage(Application.Handle, WM_SETICON...

(this won't trigger CM_ICONCHANGED

) or directly set Application.Icon

(which will also trigger CM_ICONCHANGED

). set large and small icons as needed with message WM_SETICON

. You will also need to set the class icon:

SetClassLong(Application.Handle, GCL_HICON, FIcon);

      

So whenever the app icon changes, it gets wrapped CM_ICONCHANGED

in your forms.

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

      

If you need to install this icon early on in your application (which I think is not required), follow the above steps only on the main form.

To catch / handle form icons, use WM_SETICON

a message handler in your forms:

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;

      

This should cover you in all situations.

+7


source


I just created a new VCL forms application, set the application icon in the project options and the (main only) Icon form to the same file, which I modified to make the 16x16 (32-bit depth) look different, and added the following code:

{ 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;

      

The following screen appears:

Reshaped icon



Note that both use the same .ico file, an ugly modification of ib.ico that ships with 10.2 Tokyo. I just drew some black lines and ovals only on a small icon .

I don't know the code for your GetIconHandle modification, but you can do the same here. Also note that this doesn't work if I use Icon.Handle for both. If I do this, the ugly 16x16 icon will also show up in the app drawer.

But I didn't have to change any VCL code. You can do this in any VCL application.

+4


source


There are two clear questions at the very end of the post.

  • Is it possible to let Delphi install both small and large icons without too much trouble and without modifying VCL.Forms.pas? (this is the main question);

In the context of the question, this really means asking if it is possible to force the VCL framework without modifying it to load different icon sizes. This is a very short answer: No.

Literally, however, you can use Delphi for this. The code snippets in your question are proof of this, which doesn't seem to cause too much trouble.

  1. If not, how to add the modified VCL source to your application in Delphi 10.2 Tokyo where the device interface section is changed?

A very short answer here: not possible, you cannot change the interface sections of the Delphi source files.



Now this is really the answer to the question. It's all there. However, while this information is available in the comments and answers that already exist, the question is still open. It even gives generosity. I need to see what the question really wants to ask.

The question in the title is no more explanatory than the ones included in the post.

There is an update part in the second question of the post:

Configuring icons again after installing VCL.Forms.pas, this is not a complete solution ..

It doesn't really help. After reading the update several times and reflecting on it; this update has nothing to do with what was asked as questions, or even the original information provided in the post. And I have no idea what this is talking about.


Digging more, I meet your comment on Rudy's answer :

... Delphi itself sometimes sets ICON_BIG, so in some tricky scenario you will have to catch those cases and call WM_SETICON with ICON_BIG again ....

I guess this is the problem. Although you set the appropriate icons, the VCL will overwrite your icons in some unforeseen situations. If this is not something you want to resolve, please modify your question to ask him.

I haven't seen anything like this, we don't need to modify "forms.pas" anyway to ignore the border icon setting. A simple WM_SETICON

handler in the form is enough, just don't let the message be processed.

So how are we going to install the icon? This is the answer to the WM_GETICON

documentation :

.. A window that does not have an explicitly set icon (with WM_SETICON ) uses the icon for the registered window class, ..

So, below is the solution contained in the class of forms of the proposed problem:

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;

      

Below is a screenshot of a W7 form, with a small icon ( enter image description here) in the header, a large icon ( enter image description here) in the upper left corner of the alt + tab dialog:

enter image description here

Note that XE2 has issues with its resource manager, I identified the icon as "CANCEL" in the Resources and Images dialog, but the icon in the executable turned out to be "ICON_1" as an identifier, so I had to use that in code.

+2


source







All Articles