Program icon looks curious in the title bar when u

2020-03-19 01:32发布

问题:

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.

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.

回答1:

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.



回答2:

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


回答3:

@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);