【问题标题】:Program icon looks curious in the title bar when using a VCL style使用 VCL 样式时,程序图标在标题栏中看起来很奇怪
【发布时间】:2015-11-12 13:02:23
【问题描述】:

在 Windows 7 Pro 64 位系统上使用 Delphi XE7。 如果我选择“Charcoal Dark Slate”VCL 样式,从 32x32 程序图标缩小的 16x16 像素标题栏图标看起来不像预期的那样。

它应该看起来像下面的小图标。如果我以 16x16 像素格式加载程序图标,它在标题栏中看起来不错,但在任务栏中却很难看,因为放大了 16 到 32 像素。

【问题讨论】:

  • 您应该使用包含多种尺寸的单个图标文件。
  • 我这样做了。我使用带有 16、32、48、64、96、128 像素图标的 ico 容器。
  • 顺便说一句,如果我停用 VCL 样式(使用标准 Windows 样式),所有图标看起来都不错。
  • 受此影响的每个人都应该投票/观看以下问题:quality.embarcadero.com/browse/RSP-11572 --- 自报告以来已经 3 年了,但仍未修复。如果有足够多的人投票,也许它会得到一些关注。

标签: delphi icons vcl-styles


【解决方案1】:

这是 VCL 样式 http://qc.embarcadero.com/wc/qcmain.aspx?d=106224 的已知问题

在 Embarcadero 的新 QC 网站上也可以看到这个问题:https://quality.embarcadero.com/browse/RSP-11572 --- 自最初报告以来已经 3 年了,但仍未修复。如果有足够多的人为这个问题投票,也许它会得到一些关注。

作为解决方法,您可以将正确的 16x16 图标加载到表单的 Icon 属性中。

为了使其工作,您还必须在您的 .dpr 文件中设置 Application.MainFormOnTaskBar := false;

但是,这会产生一些其他不良影响,因为它会禁用 Windows Vista 或 Windows 7 Aero 效果,包括实时任务栏缩略图、动态 Windows、Windows Flip 和 Windows Flip 3D。见:MainFormOnTaskBar

在任何情况下都不要更改您的应用程序图标大小,因为这是最糟糕的解决方案。

【讨论】:

  • 感谢 Dalija,但是任务栏图标看起来很难看。它也是 16x16 像素,但扩展为 32x32 像素。
  • AFAIK 任务栏图标使用 Application.Icon 而不是 Form.Icon
  • 没错,在应用程序上分配 32x32 图标,在表单上分配 16x16 图标。
  • 它将禁用实时任务栏实时缩略图和其他特定于 Vista 和更新 Windows 版本的效果。它们对你来说可能并不那么重要。有关其他信息,请查看文档docwiki.embarcadero.com/Libraries/Seattle/en/…
  • 如果您不使用 VCL 样式,它也不起作用。因为 VCL 只有一个图标。它需要两个。
【解决方案2】:

我终于找到了这个问题的根源,并弄清楚为什么这在没有 VCL 样式的情况下有效,而在 VCL 样式中无效。

前言:多年来,VCL 一直不支持具有多种图标大小的图标图形的概念:TIcon 始终被假定为一个单一的图形,而不是一组不同的图形尺寸和分辨率。这仍然是正确的,并且在 VCL 中可能不容易纠正一个设计问题。

VCL 将通过WM_SETICON 消息设置表单图标。 VCL 总是将wParam 设置为ICON_BIG:对VCL 源的检查表明它在设置图标时从不使用ICON_SMALL。此外,WNDCLASSEX 结构的hIconhIconSm 成员变量在创建窗口类时始终为NULL。因此,很明显 VCL 甚至从未尝试设置小图标。通常,如果一个应用程序从不设置小图标,Windows 会将大图标调整为小尺寸,这非常难看。但是,该规则有一个重要的例外。

请注意,Windows 资源文件的ICON 资源实际上会存储所谓的图标组,它是来自原始.ico 文件的一组单独的图标图像。 LoadIcon API 声明只会加载 32x32 的大图标。然而,这实际上并不完全正确。 Windows 本身似乎维护了HICON 和原始资源之间的链接,因此如果需要其他大小的图标,Windows 可以根据需要去加载它们。

这个事实没有很好的记录,但是在 MSDN 中有一个地方说明了这个事实:WNDCLASSEX structurehIconSm 变量:

与窗口类关联的小图标的句柄。如果该成员为NULL,则系统在hIcon成员指定的图标资源中搜索合适大小的图标作为小图标使用。

因此,即使 VCL 没有通过公共 TForm.Icon 类正确支持小图标(例如,通过在设计时从属性编辑器分配它),仍然可以使用其中之一来使事情正常工作两种方法:

  • 不设置TForm.Icon 属性(无图标)。在这种情况下,表单将从TApplication.Icon 获取图标。它的默认值来自应用程序的MAINICON 资源。来自TApplication.Create
    FIcon := TIcon.Create;
    FIcon.Handle := LoadIcon(MainInstance, 'MAINICON');
  • 如果不想使用应用默认图标,可以在运行时加载不同的图标资源;在 C++ 中:
    myForm->Icon->LoadFromResourceName(FindHInstance(...), L"OtherResource");

因此,VCL 提供了对小图标的基本支持,因为它支持从资源加载图标,而 Windows 支持从从资源加载的大图标加载小图标。

问题: VCL 样式显然不依赖于非客户区(例如标题栏)的 Windows 默认呈现行为。相反,它自己处理所有渲染。渲染中的一项任务是 VCL 样式必须确定要渲染的图标。事实证明,即使主要的 VCL 表单类不支持小图标 - VCL 样式挂钩可以!嗯,有点。这发生在TFormStyleHook.GetIcon:

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

问题是钩子用ICON_SMALL而不是ICON_SMALL2调用WM_GETICON。来自 MSDN:

  • ICON_SMALL: 检索窗口的小图标。
    在上面的代码中,这将返回NULL,因为VCL没有设置小图标。
  • ICON_SMALL2:检索应用程序提供的小图标。 如果应用程序没有提供图标,系统将使用系统为该窗口生成的图标。(强调我的)
    在上面的代码中,这将返回一个真实的HICON:问题是系统如何生成图标?我的实验表明,无论如何,你会得到一个小图标:
    • 如果大图标链接到具有适当图标大小的底层 Windows 资源,则返回该图标。
    • 否则,系统将调整另一个图标的大小以适合所需的小图标尺寸,然后返回。

修复: VCL 在调用WM_GETICON 时需要使用ICON_SMALL2 而不是ICON_SMALL。 (注意TFormStyleHook.TMainMenuBarStyleHook.GetIcon 中有类似的代码也需要修复。)由于这是一个非常简单的修复,我希望 Embarcadero 尽快应用它。

解决方法: 在 VCL 修复之前,您必须制作自己的派生表单挂钩。不幸的是,TFormStyleHook.GetIcon 是私人的,很难找到。所以我们尝试了一种不同的技术:当wParamICON_SMALL 时,改变WM_GETICON 的消息处理行为,这样它就会变成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));

【讨论】:

  • 不错的发现。感谢您挖掘这个。希望这些信息可以加快 Embarcadero 方面的正确修复。
  • 谢谢,詹姆斯,非常棒的工作。我使用它以及其他错误修复来更正 VCL 样式应用程序的标题栏和 Windows 显示缩放。 stackoverflow.com/a/41370678/4206370
【解决方案3】:

@詹姆斯约翰斯顿,

谢谢!尽管发布的代码中有一个小故障,但它对我来说很好用:

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

删除PreventRecursion = false;

这是代码的 Delphi 端口:

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.

然后在你的 DPR 项目中:

TStyleManager.Engine.RegisterStyleHook(TForm, TFixedFormStyleHook);
TStyleManager.Engine.RegisterStyleHook(TCustomForm, TFixedFormStyleHook);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-01-16
    • 2018-08-14
    • 1970-01-01
    • 1970-01-01
    • 2017-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多