7

Using Delphi XE7 on a Windows 7 Pro 64-bit system. If I choose 'Charcoal Dark Slate' VCL style, the 16x16 pixel titel bar icon down-sized from the 32x32 program icon looks not like expected. enter image description here

It should look like the small icon below. If I load the program icon in 16x16 pixel format, it looks good in the titel bar, but ugly in the task bar because of the 16 to 32 pixel enlargement.

Michael
  • 3,982
  • 4
  • 30
  • 46
Nobby
  • 289
  • 2
  • 12
  • You should be using a single icon file which contains multiple sizes. – Jerry Dodge Nov 12 '15 at 13:16
  • I do so. I use a ico container with 16, 32, 48, 64, 96, 128 pixel icons. – Nobby Nov 12 '15 at 13:18
  • Apropos, if I deactivate VCL styles (using standard Windows style), all icons looks good. – Nobby Nov 12 '15 at 13:20
  • 1
    Everyone affected by this should vote for / watch the following issue: https://quality.embarcadero.com/browse/RSP-11572 --- it's been 3 years since reported, and still not fixed. If enough people vote, maybe it will get some attention. – James Johnston Nov 30 '15 at 20:15

3 Answers3

6

This is known issue with VCL Styles http://qc.embarcadero.com/wc/qcmain.aspx?d=106224

Also see this issue in Embarcadero's newer QC site: https://quality.embarcadero.com/browse/RSP-11572 --- it's been 3 years since initially reported, and still not fixed. If enough people vote for that issue, maybe it will get some attention.

As workaround you can load proper 16x16 icon into form's Icon property.

In order for that to work you have to also set Application.MainFormOnTaskBar := false; in your .dpr file

However that has some other undesirable effects because it will disable Windows Vista or Windows 7 Aero effects, including live taskbar thumbnails, Dynamic Windows, Windows Flip, and Windows Flip 3D. See: MainFormOnTaskBar

In any case do not change your application icon size because it is the worst solution.

James Johnston
  • 9,264
  • 9
  • 48
  • 76
Dalija Prasnikar
  • 27,212
  • 44
  • 82
  • 159
  • Thanks Dalija, but then the task bar icon looks ugly. It's also 16x16 pixel but expanded to 32x32 pixel. – Nobby Nov 12 '15 at 13:43
  • AFAIK taskbar icon uses Application.Icon not Form.Icon – Dalija Prasnikar Nov 12 '15 at 13:44
  • That's correct, assign the 32x32 icon on the application, and the 16x16 icon on the form(s). – Jerry Dodge Nov 12 '15 at 13:48
  • @JerryDodge actually, if that works only if MainFormOnTaskBar is false – Dalija Prasnikar Nov 12 '15 at 13:56
  • If MainFormOnTaskBar is false, both icons looks good. But what are the disadvantages? – Nobby Nov 12 '15 at 14:06
  • 1
    It will disable live task bar live thumbnails and other effects specific for Vista and newer Windows version. They may not be so important to you. For additional info check documentation http://docwiki.embarcadero.com/Libraries/Seattle/en/Vcl.Forms.TApplication.MainFormOnTaskBar – Dalija Prasnikar Nov 12 '15 at 14:08
  • The right way to solve this is to steer away from the VCL and use WM_SETICON for both small and large icons. – David Heffernan Nov 12 '15 at 15:09
  • @DavidHeffernan WM_SETICON will not work because VCL Styles mess it up. The real solution is fixing forms VCLStyle hook. – Dalija Prasnikar Nov 12 '15 at 15:16
  • You still have to set both small and large icons. – David Heffernan Nov 12 '15 at 15:22
  • It will work if you stop anything else screwing with the icons – David Heffernan Nov 12 '15 at 15:34
  • @DavidHeffernan You don't get it. If you don't use VCL Styles there are no problems in displaying application icon. If you use them they screw it up. There are three options 1. don't use VCL Styles, 2. use workaround from my answer 3. fix broken VCL code. – Dalija Prasnikar Nov 12 '15 at 15:57
  • 1
    If you don't use VCL styles it doesn't work either. Because the VCL has only one icon. It needs two. – David Heffernan Nov 12 '15 at 16:15
  • @DavidHeffernan If you don't use VCL Styles then Windows take over icon painting and they draw proper icon dimension in proper place using Application.MainIcon if you provide icon with different sizes, and even if you don't provide all sizes Windows icon resizing uses better resizing algorithms than Delphi and result is much smoother anyway. – Dalija Prasnikar Nov 12 '15 at 19:26
  • No. That's not right. Plain vanilla VCL doesn't get it right. – David Heffernan Nov 12 '15 at 19:48
  • @DavidHeffernan: Not quite true. On XE8, I find that plain VCL without style gets it right if I set TApplication::Icon via project properties. But if I directly set form's Icon property, it gets set wrong. If VCL styles are used, it's always wrong. – James Johnston Nov 30 '15 at 20:14
  • @James No, plain vanilla VCL is always wrong, always has been, because it only sets one icon. It should set both large and small, but only sets one, large I think. – David Heffernan Nov 30 '15 at 21:07
  • @DavidHeffernan: Did you try it? (1) Make new VCL forms project. (2) Assign .ICO file with both 16x16 and 32x32 to Application Icon in Project Options. (3) Run the project. VCL gets it right on C++ Builder XE8, at any rate - if you use TApplication::Icon (e.g. like via above example). – James Johnston Dec 01 '15 at 17:08
  • @James whilst an .ico file can have multiple images an HICON has but one. The VCL loads one from the MAINICON resource and sends wm_seticon once. With ICON_BIG IIRC. What happens is that the small icon is made by resizing the large one. I'm not sure whether or not Emba are aware of the issue. – David Heffernan Dec 01 '15 at 17:21
  • @James http://stackoverflow.com/questions/8767191/blurred-delphi-form-icon-in-windows-7-taskbar – David Heffernan Dec 01 '15 at 17:24
  • @DavidHeffernan It does not matter what Delphi does right or wrong. Point is that Windows recognize multiple icon sizes in application MainIcon and display proper one when VCL Styles are not turned on. Just download attached project from my QC report and see for yourself. If you want to dissect VCL code and explain why is that happening, please do and report back. – Dalija Prasnikar Dec 01 '15 at 18:55
  • @Dalija No it does not. Because the VCL is loading the icons into TForm.Icon and then sending a WM_SETICON with ICON_BIG. It doesn't sen ICON_SMALL. Try with a 16px icon that is green and 32px icon that is red. See if you can get a form to display the different icons. – David Heffernan Dec 01 '15 at 18:58
  • @DavidHeffernan I am not setting TForm.Icon. If you specifically set Form.Icon then it behaves the way you say, but if you don't than application icon is shown properly in all dimensions on both Form and TaskBar (large or small). – Dalija Prasnikar Dec 01 '15 at 19:05
  • Is that a change in behaviour in the VCL from older versions? Still, the entire design is wrong when you do need to specify an icon and can only provide one. – David Heffernan Dec 01 '15 at 19:36
  • @DavidHeffernan: What you are saying simply does not seem true for TApplication::Icon for both C++ Builder 5 and XE8 - this is not a change in the VCL behavior, it has been like this since forever. Again, please try my steps above and you'll see that the true/correct 16x16 and 32x32 icons are simultaneously used on both small system menu and large taskbar icon / Alt+Tab icon, and NO SCALING is occurring. Do your own test with red/green icons and you will see I am right. :) It is TForm::Icon that is broken, and also VCL styles that is broken. – James Johnston Dec 01 '15 at 19:58
  • @James TApplication::Icon wraps an icon. That's a single image. – David Heffernan Dec 01 '15 at 20:01
  • @DavidHeffernan: I am not disagreeing with you on all points - there's a rather clear call to LoadIcon in Vcl.Forms.pas when populating TApplication::Icon, but I'd be interested to know exactly what's going on under the covers that makes the behavior I outline "work". Perhaps Windows does not use the window's icon at all, and uses the first icon in the EXE instead directly for the taskbar & Alt+Tab icons? At any rate, without VCL styles, the proper 16x16 and 32x32 are both being used on my system, without any special hoop-jumping what-so-ever. It just "works".... try it! – James Johnston Dec 01 '15 at 20:54
  • @James A call to LoadIcon is no good. That returns a single HICON. No way to get it to do two different sizes. Windows uses the icons associated with the top level window for what you describe. Instead of telling me to "try it", consider that I have done. I've been working around this for over 10 years. Perhaps modern Delphi is better. I find it hard to see how. The VCL has to provide both small and large icons. Last time I looked it did not. – David Heffernan Dec 01 '15 at 21:01
  • @DavidHeffernan Instead of going in circles, just download the darn QC project and see for yourself. There is even exe inside :) – Dalija Prasnikar Dec 01 '15 at 21:06
  • I'm not going round in circles – David Heffernan Dec 01 '15 at 21:08
  • @DavidHeffernan Yes, you are. What happens is not what you think is happening. Not that your explanations are wrong, but the end result when you use Application.Main icon just works. How, and why I have no idea, but it works. – Dalija Prasnikar Dec 01 '15 at 21:11
  • Hardly convincing. Not sure what Application.Main is. I'll look at this when I'm at a computer again. I thought I knew how winapi and VCL worked. – David Heffernan Dec 01 '15 at 21:13
  • @DavidHeffernan: Since you are disbelieving us, check out this series of images: http://imgur.com/a/0s0ac --- which you can trivially reproduce using my steps above. Like Dalija says, it "just works." – James Johnston Dec 01 '15 at 22:25
  • @James Something has changed then. You do agree though that an HICON only holds one icon? – David Heffernan Dec 01 '15 at 22:44
  • @DavidHeffernan: This behavior works even in C++ Builder 5, so if something changed in the VCL, it was in the 1990s. Note that I am testing all of these VCL versions on Windows 7. Yes, I do agree that an HICON holds only one icon size. :) – James Johnston Dec 01 '15 at 22:58
  • @James That version doesn't have the main form on the taskbar. The application window is the one associated with the taskbar. Perhaps that's the difference. – David Heffernan Dec 01 '15 at 23:04
  • @DavidHeffernan: Sorry I had forgotten, but you are right. But I must clarify that in BCB5, we had hacked/worked around VCL limitations in that area, and made the main form to not have an owner. In this situation, the icon behavior was identical to what I am showing above. That is why I got confused with my statement regarding BCB5 above. (Basically BCB5 was made to act like newer CB versions with MainFormOnTaskBar set to true and PopupMode to pmAuto.) – James Johnston Dec 01 '15 at 23:34
  • @James Perhaps what happens, is that Windows can recognise a shared icon loaded with LoadIcon and knows where it came from. And so can go back there to find a different sized icon. I don't see any documentation of that behaviour, but perhaps it exists. One wonders what happens if the icon sizes don't exactly match what's in the resource. – David Heffernan Dec 02 '15 at 04:52
  • @DavidHeffernan If you don't have exact icon size included, Windows take next larger icon and resize it. For instance if you delete 16x16 icon, and have 24x24 available it will use that one and if you don't have 24x24 it will use 32x32 (if available) and so on. As far as I can see there is no difference between default Windows resized icon and VCL Styles ones. – Dalija Prasnikar Dec 02 '15 at 08:13
  • @DavidHeffernan Yes, HICON holds only single icon. It is just Windows doing some voodoo (because we don't know what exactly is going on) with provided MAINICON resource that can contain multiple icon sizes. Project Options -> Application -> Icon. – Dalija Prasnikar Dec 02 '15 at 08:17
  • @Dalija This must be magic behaviour for shared icons, e.g. those returned by LoadIcon. Nothing to do with MAINICON name. Windows doesn't know about that. VCL calls LoadIcon, sends WM_SETICON, and Windows does the rest. Won't happen for non shared icons. – David Heffernan Dec 02 '15 at 08:18
  • @DavidHeffernan: "Perhaps what happens, is that Windows can recognise a shared icon loaded with LoadIcon and knows where it came from. And so can go back there to find a different sized icon" --- that is exactly what it does. "I don't see any documentation of that behaviour, but perhaps it exists." --- I found the documentation; see my new answer to this question. "This must be magic behaviour for shared icons" --- now it's not magic, because it's documented. :) – James Johnston Jan 28 '16 at 17:21
6

I finally got to the bottom of this issue, and figured out why this worked without VCL styles, and doesn't work with VCL styles.

Preface: For years, VCL has not supported the concept of an icon graphic with multiple icon sizes: a TIcon was always assumed to be one single graphic - not a set of graphics of varying dimensions and resolutions. This is still true, and a design issue probably not easy to correct in the VCL.

The VCL will set the form icon by way of the WM_SETICON message. VCL always sets wParam to ICON_BIG: an examination of VCL sources shows it never uses ICON_SMALL when setting the icon. Additionally, the hIcon and hIconSm member variables of WNDCLASSEX structure are always NULL when creating the window class. Therefore, it's clear that VCL never even attempts to set a small icon. Normally, if an app never sets a small icon, Windows will resize the large icon to be the small size, which is quite ugly. However, there is an important exception to that rule.

Note that a Windows resource file's ICON resource will actually store what is known as an icon group, which is a set of the individual icon images from the original .ico file. The LoadIcon API states that only the large 32x32 icon will be loaded. However, this is not actually strictly true. It seems that Windows itself maintains a link between an HICON and the original resource, so that if icons of other sizes are required, Windows can go load them as needed.

This fact is not well-documented, but there is one place in MSDN that states this fact: WNDCLASSEX structure, hIconSm variable:

A handle to a small icon that is associated with the window class. If this member is NULL, the system searches the icon resource specified by the hIcon member for an icon of the appropriate size to use as the small icon.

Therefore, even though VCL did not support small icons properly by way of the public TForm.Icon class (e.g. by assigning it from the property editor at design time), it was still possible to get things working right using one of these two methods:

  • Leave the TForm.Icon property unset (no icon). In that case, the form will get the icon from TApplication.Icon. The default value of this comes from the application's MAINICON resource. From TApplication.Create:
    FIcon := TIcon.Create;
    FIcon.Handle := LoadIcon(MainInstance, 'MAINICON');
  • If you don't want to use the application default icon, you can load a different icon resource at runtime; in C++:
    myForm->Icon->LoadFromResourceName(FindHInstance(...), L"OtherResource");

Therefore, VCL provides basic support for small icons, because it supports loading icons from resources, and Windows supports loading small icons from large icons that were loaded from a resource.

The problem: VCL styles obviously does not rely on Windows-default rendering behavior for non-client areas, such as the title bar. Instead, it handles all the rendering itself. One task in rendering is that VCL styles must determine what icon to render. As it turns out, even though the main VCL form classes don't support small icons - the VCL styles hook does! Well, sort of. This happens in TFormStyleHook.GetIcon:

TmpHandle := THandle(SendMessage(Handle, WM_GETICON, ICON_SMALL, 0));
if TmpHandle = 0 then
  TmpHandle := THandle(SendMessage(Handle, WM_GETICON, ICON_BIG, 0));

The problem is that the hook calls WM_GETICON with ICON_SMALL and not ICON_SMALL2. From MSDN:

  • ICON_SMALL: Retrieve the small icon for the window.
    In the above code, this will return NULL because VCL is not setting a small icon.
  • ICON_SMALL2: Retrieves the small icon provided by the application. If the application does not provide one, the system uses the system-generated icon for that window. (emphasis mine)
    In the above code, this would return a real HICON: the question is how does the system generate the icon? My experiments show that one way or another, you'll get a small icon:
    • If the large icon is linked to an underlying Windows resource that has an appropriate icon size, then that icon is returned.
    • Otherwise, the system will resize another icon to fit the required small icon dimensions, and return that.

The fix: VCL needs to use ICON_SMALL2 instead of ICON_SMALL whenever calling WM_GETICON. (Note there is similar code in TFormStyleHook.TMainMenuBarStyleHook.GetIcon that will also require fixing.) Since this is such a trivially easy fix, I hope Embarcadero applies it soon.

The workaround: Until VCL is fixed, you have to make your own derived form hook. Unfortunately, TFormStyleHook.GetIcon is private, and really hard to get to. So we try a different technique: alter message handling behavior for WM_GETICON when wParam is ICON_SMALL so that it will instead be like ICON_SMALL2.

class TFixedFormStyleHook : public TFormStyleHook
{
public:
    bool PreventRecursion;
    __fastcall virtual TFixedFormStyleHook(TWinControl* AControl)
        : TFormStyleHook(AControl), PreventRecursion(false) {}
    virtual void __fastcall WndProc(TMessage &Message)
    {
        if (Message.Msg == WM_GETICON && Message.WParam == ICON_SMALL &&
            !PreventRecursion && this->Control &&
            this->Control->HandleAllocated())
        {
            // Just in case some future Windows version decides to call us again
            // with ICON_SMALL as response to being called with ICON_SMALL2.
            PreventRecursion = true;

            Message.Result = SendMessage(this->Control->Handle, WM_GETICON,
                ICON_SMALL2, Message.LParam);
            PreventRecursion = false;
            this->Handled = true;
        } else {
            this->TFormStyleHook::WndProc(Message);
        }
    }
};

// In your WinMain function, you have to register your hook for every data
// type that VCL originally registered TFormStyleHook for:
TCustomStyleEngine::RegisterStyleHook(__classid(TForm),
    __classid(TFixedFormStyleHook));
TCustomStyleEngine::RegisterStyleHook(__classid(TCustomForm),
    __classid(TFixedFormStyleHook));
Wolf
  • 9,679
  • 7
  • 62
  • 108
James Johnston
  • 9,264
  • 9
  • 48
  • 76
  • 1
    Nice findings. Thanks for digging this up. Hopefully, this information can speed up proper fix on Embarcadero side. – Dalija Prasnikar Jan 28 '16 at 19:23
  • Thanks, James, very nice work. I used this, together with other bug fixes, to correct the title bar on a VCL styled application and Windows display scaling. http://stackoverflow.com/a/41370678/4206370 – Tom Major Dec 28 '16 at 22:38
2

@James Johnston,

Thank you! It works fine for me although there was a small glitch in the posted code:

Message.Result = SendMessage(this->Control->Handle, WM_GETICON,
    ICON_SMALL2, Message.LParam);
PreventRecursion = false;
this->Handled = true;

Remove PreventRecursion = false;

Here is the Delphi port of your code:

unit FixIconHook;

interface
uses
  WinAPI.Windows,
  WinAPI.Messages,
  Vcl.Graphics,
  Vcl.Controls,
  Vcl.Forms,
  Vcl.Themes;

type
  TFixedFormStyleHook = class(TFormStyleHook)
  private
    PreventRecursion: Boolean;
  strict protected
    procedure WndProc(var Message: TMessage); override;
  public
    constructor Create(AControl: TWinControl); override;
  end;

implementation

constructor TFixedFormStyleHook.Create(AControl: TWinControl);
begin
  inherited Create(AControl);
  PreventRecursion := False;
end;

procedure TFixedFormStyleHook.WndProc(var Message: TMessage);
begin
  if (Message.Msg = WM_GETICON) and (Message.WParam = ICON_SMALL) and 
    (not PreventRecursion) and (Assigned(Control) and 
    Control.HandleAllocated) then
  begin
    // Just in case some future Windows version decides to call us again
    // with ICON_SMALL as response to being called with ICON_SMALL2.
    PreventRecursion := true;
    Message.Result := SendMessage(Control.Handle, 
      WM_GETICON, ICON_SMALL2, Message.LParam);
    Handled := true;
    exit;
  end;
  inherited WndProc(Message);
end;

end.

Then in your DPR project:

TStyleManager.Engine.RegisterStyleHook(TForm, TFixedFormStyleHook);
TStyleManager.Engine.RegisterStyleHook(TCustomForm, TFixedFormStyleHook);
Wolf
  • 9,679
  • 7
  • 62
  • 108
GuiDesign
  • 56
  • 3