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.
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
orDestroy
not found in the base class. In previous versions of Delphi, writing simpleclass
without any ancestor implicitly inherited it fromTObject
, but when I compile the code from the command linedcc32
with command line parametersdcc32 -Q -M -$D- -$M+
, this error occurs whenCreate
or isDestroy
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 changing
VCL.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
orDestroy
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!
source to share
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.
source to share
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:
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.
source to share
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.
- 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 ( ) in the header, a large icon ( ) in the upper left corner of the alt + tab dialog:
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.
source to share