我终于找到了这个问题的根源,并弄清楚为什么这在没有 VCL 样式的情况下有效,而在 VCL 样式中无效。
前言:多年来,VCL 一直不支持具有多种图标大小的图标图形的概念:TIcon 始终被假定为一个单一的图形,而不是一组不同的图形尺寸和分辨率。这仍然是正确的,并且在 VCL 中可能不容易纠正一个设计问题。
VCL 将通过WM_SETICON 消息设置表单图标。 VCL 总是将wParam 设置为ICON_BIG:对VCL 源的检查表明它在设置图标时从不使用ICON_SMALL。此外,WNDCLASSEX 结构的hIcon 和hIconSm 成员变量在创建窗口类时始终为NULL。因此,很明显 VCL 甚至从未尝试设置小图标。通常,如果一个应用程序从不设置小图标,Windows 会将大图标调整为小尺寸,这非常难看。但是,该规则有一个重要的例外。
请注意,Windows 资源文件的ICON 资源实际上会存储所谓的图标组,它是来自原始.ico 文件的一组单独的图标图像。 LoadIcon API 声明只会加载 32x32 的大图标。然而,这实际上并不完全正确。 Windows 本身似乎维护了HICON 和原始资源之间的链接,因此如果需要其他大小的图标,Windows 可以根据需要去加载它们。
这个事实没有很好的记录,但是在 MSDN 中有一个地方说明了这个事实:WNDCLASSEX structure,hIconSm 变量:
与窗口类关联的小图标的句柄。如果该成员为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 是私人的,很难找到。所以我们尝试了一种不同的技术:当wParam 是ICON_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));