使用 VCL 样式时,标题栏中的程序图标看起来很奇怪
Program icon looks curious in the title bar when using a VCL style
在 Windows 7 Pro 64 位系统上使用 Delphi XE7。
如果我选择 'Charcoal Dark Slate' VCL 样式,32x32 程序图标中的 16x16 像素标题栏图标 down-sized 看起来不像预期的那样。
它应该看起来像下面的小图标。如果我以 16x16 像素格式加载程序图标,它在标题栏中看起来不错,但在任务栏中看起来很丑,因为放大了 16 到 32 像素。
这是 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 翻转, 和 Windows 翻转 3D。参见:MainFormOnTaskBar
在任何情况下都不要更改您的应用程序图标大小,因为这是最糟糕的解决方案。
我终于找到了这个问题的根源,并弄清楚了为什么这在没有 VCL 样式的情况下有效,而在 VCL 样式中不起作用。
前言: 多年来,VCL 一直不支持具有多种图标大小的图标图形的概念:TIcon
始终被假定为一个图形 -不是一组不同尺寸和分辨率的图形。这仍然是正确的,一个设计问题可能不容易在 VCL 中更正。
VCL 将通过 WM_SETICON
消息设置窗体图标。 VCL 总是将 wParam
设置为 ICON_BIG
:对 VCL 源代码的检查表明它在设置图标时从不使用 ICON_SMALL
。此外,WNDCLASSEX
结构的hIcon
和hIconSm
成员变量在创建windowclass时总是NULL
。因此,很明显 VCL 甚至从未尝试设置一个小图标。通常,如果一个应用程序从来没有设置过小图标,Windows 会将大图标调整为小图标,这非常难看。但是,该规则有一个重要的例外。
请注意,Windows 资源文件的 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 指出只会加载 32x32 大图标。然而,这实际上并非严格正确。似乎Windows本身在HICON
和原始资源之间维护了一个link,所以如果需要其他尺寸的图标,Windows可以根据需要加载它们。
这个事实不是 well-documented,但 MSDN 中有一个地方说明了这个事实:WNDCLASSEX
structure,hIconSm
变量:
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.
因此,即使 VCL 没有通过 public TForm.Icon
class 正确支持小图标(例如,通过在设计时从 属性 编辑器分配它时间),仍然可以使用以下两种方法之一使事情正常工作:
- 保留
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-default 渲染行为 non-client 区域,例如标题栏。相反,它自己处理所有渲染。渲染中的一项任务是 VCL 样式必须确定要渲染的图标。事实证明,尽管主要的 VCL 表单 classes 不支持小图标 - 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));
问题是挂钩调用 WM_GETICON
时使用 ICON_SMALL
而不是 ICON_SMALL2
。来自 MSDN:
ICON_SMALL
: 检索 window. 的小图标
在上面的代码中,这将return NULL
因为VCL没有设置小图标。
ICON_SMALL2
:获取应用程序提供的小图标。 如果应用程序没有提供,系统会使用 system-generated 图标作为 window。(强调我的)
在上面的代码中,这会return一个真正的HICON
:问题是系统是如何生成图标的?我的实验表明,不管怎样,你都会得到一个小图标:
- 如果将大图标 link 编辑到具有适当图标大小的基础 Windows 资源,则该图标 return 编辑。
- 否则,系统将调整另一个图标的大小以适应所需的小图标尺寸,并且return那个。
修复: 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));
@James Johnston,
谢谢!虽然发布的代码中有一个小故障,但对我来说效果很好:
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);
在 Windows 7 Pro 64 位系统上使用 Delphi XE7。
如果我选择 'Charcoal Dark Slate' VCL 样式,32x32 程序图标中的 16x16 像素标题栏图标 down-sized 看起来不像预期的那样。
它应该看起来像下面的小图标。如果我以 16x16 像素格式加载程序图标,它在标题栏中看起来不错,但在任务栏中看起来很丑,因为放大了 16 到 32 像素。
这是 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 翻转, 和 Windows 翻转 3D。参见:MainFormOnTaskBar
在任何情况下都不要更改您的应用程序图标大小,因为这是最糟糕的解决方案。
我终于找到了这个问题的根源,并弄清楚了为什么这在没有 VCL 样式的情况下有效,而在 VCL 样式中不起作用。
前言: 多年来,VCL 一直不支持具有多种图标大小的图标图形的概念:TIcon
始终被假定为一个图形 -不是一组不同尺寸和分辨率的图形。这仍然是正确的,一个设计问题可能不容易在 VCL 中更正。
VCL 将通过 WM_SETICON
消息设置窗体图标。 VCL 总是将 wParam
设置为 ICON_BIG
:对 VCL 源代码的检查表明它在设置图标时从不使用 ICON_SMALL
。此外,WNDCLASSEX
结构的hIcon
和hIconSm
成员变量在创建windowclass时总是NULL
。因此,很明显 VCL 甚至从未尝试设置一个小图标。通常,如果一个应用程序从来没有设置过小图标,Windows 会将大图标调整为小图标,这非常难看。但是,该规则有一个重要的例外。
请注意,Windows 资源文件的 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 指出只会加载 32x32 大图标。然而,这实际上并非严格正确。似乎Windows本身在HICON
和原始资源之间维护了一个link,所以如果需要其他尺寸的图标,Windows可以根据需要加载它们。
这个事实不是 well-documented,但 MSDN 中有一个地方说明了这个事实:WNDCLASSEX
structure,hIconSm
变量:
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.
因此,即使 VCL 没有通过 public TForm.Icon
class 正确支持小图标(例如,通过在设计时从 属性 编辑器分配它时间),仍然可以使用以下两种方法之一使事情正常工作:
- 保留
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-default 渲染行为 non-client 区域,例如标题栏。相反,它自己处理所有渲染。渲染中的一项任务是 VCL 样式必须确定要渲染的图标。事实证明,尽管主要的 VCL 表单 classes 不支持小图标 - 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));
问题是挂钩调用 WM_GETICON
时使用 ICON_SMALL
而不是 ICON_SMALL2
。来自 MSDN:
ICON_SMALL
: 检索 window. 的小图标
在上面的代码中,这将returnNULL
因为VCL没有设置小图标。ICON_SMALL2
:获取应用程序提供的小图标。 如果应用程序没有提供,系统会使用 system-generated 图标作为 window。(强调我的)
在上面的代码中,这会return一个真正的HICON
:问题是系统是如何生成图标的?我的实验表明,不管怎样,你都会得到一个小图标:- 如果将大图标 link 编辑到具有适当图标大小的基础 Windows 资源,则该图标 return 编辑。
- 否则,系统将调整另一个图标的大小以适应所需的小图标尺寸,并且return那个。
修复: 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));
@James Johnston,
谢谢!虽然发布的代码中有一个小故障,但对我来说效果很好:
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);