SetClassLong(hWnd, GCL_HICON, hIcon) 无法替换 WinForms Form.Icon

发布于 2024-08-22 00:03:07 字数 1404 浏览 9 评论 0原文

我想使用特定的 ICO 文件作为 WinForms 应用程序的图标。由于我希望能够在 Alt-Tabbing 时为标题栏指定一个小图标 (16x16) 和一个普通图标 (32x32),因此我无法使用接受单个 < 的 Form.Icon 属性。 code>System.Drawing.Icon 对象,这迫使我使用低分辨率图标或普通图标。

我发布了一个相关问题想出了一个非常简单的解决方案,适用于本机 Win32 应用程序:

SetClassLong(hWnd, GCL_HICON, hIcon32x32);
SetClassLong(hWnd, GCL_HICONSM, hIcon16x16);

尝试在 Form 上应用相同的技巧是行不通的。我有以下 P/Invoke 定义:

[DllImport ("User32.dll")]
extern static int SetClassLong(System.IntPtr hWnd, int index, int value);

const int GCL_HICON   = -14;
const int GCL_HICONSM = -34;

然后我只需调用:

System.IntPtr hIcon32x32 = ...;
System.IntPtr hIcon16x16 = ...;
SetClassLong(this.Handle, GCL_HICON, hIcon32x32.ToInt32());
SetClassLong(this.Handle, GCL_HICONSM, hIcon16x16.ToInt32());

并且从不调用 Form.Icon。然而,这不起作用:

  1. 表单中的图标仍然是 WinForms 提供的默认图标。
  2. 当按 Alt-Tab 时,我仍然看到 WinForms 默认图标。

...但是,有趣的是,当我按 Alt-Tab 时,我会在非常短的时间内看到我使用 GCL_HICON 定义的图标(或者 GCL_HICONSM 如果我不要使用GCL_HICON)。幕后似乎发生了一些事情,迫使 Windows 使用 WinForms 默认图标绘制图标。

我不明白我做错了什么以及幕后发生了什么。

编辑:我真的希望能够提供动态创建的两个不同的图标,而不是将 Form.Icon 绑定到磁盘上的图标。这就是为什么我尝试使用 P/Invoke 代码来指定内存中的图标。

I'd like to use a specific ICO file as my icon with a WinForms application. Since I want to be able to specify a small icon (16x16) for the title bar and a normal icon (32x32) when Alt-Tabbing, I cannot use the Form.Icon property which accepts a single System.Drawing.Icon object, which forces me to use either the low res icon, or the normal icon.

I posted a related question which came up with a very simple solution which works fine for native Win32 applications:

SetClassLong(hWnd, GCL_HICON, hIcon32x32);
SetClassLong(hWnd, GCL_HICONSM, hIcon16x16);

Trying to apply the same trick on a Form does not work. I have following P/Invoke definitions:

[DllImport ("User32.dll")]
extern static int SetClassLong(System.IntPtr hWnd, int index, int value);

const int GCL_HICON   = -14;
const int GCL_HICONSM = -34;

and I then simply call:

System.IntPtr hIcon32x32 = ...;
System.IntPtr hIcon16x16 = ...;
SetClassLong(this.Handle, GCL_HICON, hIcon32x32.ToInt32());
SetClassLong(this.Handle, GCL_HICONSM, hIcon16x16.ToInt32());

and never call Form.Icon. This does not work, however:

  1. The icon in the form is still the default WinForms provided icon.
  2. When pressing Alt-Tab, I still see the WinForms default icon.

...but, what's interesting, is that when I press Alt-Tab, I see for a very very short moment the icon I defined using GCL_HICON (or GCL_HICONSM if I do not use GCL_HICON). Something seems to be happening behind the scenes, which forces Windows to paint the icon using the WinForms default icon.

I can't figure out what I've done wrong and what is going on behind the scenes.

edited: I really want to be able to provide two different icons created on the fly, not bind the Form.Icon to an icon on disk. That's why I am trying to use the P/Invoke code to specify the icons in memory.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

羞稚 2024-08-29 00:03:08

我实际上还没有通过测试或查看反汇编的 WinForms 代码来验证这一点,所以我不确定这个答案是否满足“可信和/或官方来源”的赏金条件。但我认为我非常可信,所以无论如何我都会尝试一下!

您正在设置与窗口关联的图标。您可以使用 SetClassLong[Ptr] 函数和 GCL_HICON/GCL_HICONSM 索引来完成此操作,但它与在WNDCLASSEX 结构位于班级注册时间。这会设置该类窗口的默认图标。

但是,各个窗口可以设置自己的图标,覆盖其类提供的默认图标。您可以通过发送 WM_SETICON 消息,传递 ICON_BIGICON_SMALL 作为 wParam 以及指向图标作为lParam。据推测,这就是 WinForms 正在做的事情。这就是为什么显示“默认”WinForms 图标而不是您分配的默认窗口类图标的原因,因为 WinForms 使用 WM_SETICON 设置其默认图标,而不是通过窗口类。关于 WinForms 图标的唯一“默认”是,如果您不分配不同的自定义图标,它会由框架自动分配。它不符合“默认”的任何其他定义——当然不符合从 Win32 角度可能使用的定义。

Form.Icon 属性肯定使用 WM_SETICON 来修改图标,这就是它按预期工作的原因。现在,您说您不想设置 Icon 属性,因为

我真的希望能够提供动态创建的两个不同的图标,而不是将 Form.Icon 绑定到磁盘上的图标。这就是为什么我尝试使用 P/Invoke 代码来指定内存中的图标。

但这并不意味着您无法设置 Icon 属性。您可以在此处指定图标的句柄 (HICON),就像使用 P/Invoke 时一样。您所需要的只是静态 Icon.FromHandle< /code>方法,该方法根据指定的 HICON 创建一个新的 Icon 对象。然后,将此 Icon 对象分配给表单的 Icon 属性。

但你不必这样做。如果需要,您可以使用 P/Invoke:

const int WM_SETICON = 0x80;

enum IconType
{
    ICON_BIG   = 1;
    ICON_SMALL = 0;
}

[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd,
                                 int message,
                                 IntPtr wParam,
                                 IntPtr lParam);

然后,像您所拥有的那样调用它:

IntPtr hIcon32x32 = ...;
IntPtr hIcon16x16 = ...;
SendMessage(this.Handle, WM_SETICON, (IntPtr)IconType.ICON_BIG, hIcon32x32);
SendMessage(this.Handle, WM_SETICON, (IntPtr)IconType.ICON_SMALL, hIcon16x16);

只有一件事您做错了:假设“大”图标始终为 32x32 像素,而“小”图标始终为 32x32 像素为 16x16 像素。至少,我假设您是根据变量的名称来执行此操作的。如果是这样,那就是一个无效的假设。这些只是最常见的尺寸。不保证它们在所有环境中都相同。这就是为什么在 .ico 文件中提供更大的图标很重要;例如,48x48 图标。由于您动态设置图标,Windows 将无法访问较大的图标来进行缩减采样,并且当您的 32x32 图标放大时,您可能会得到一些非常模糊和丑陋的东西。

要检索实际尺寸,请调用GetSystemMetrics函数。 SM_CXICONSM_CYICON 标志将分别告诉您“大”图标的 X 和 Y 尺寸。 SM_CXSMICONSM_CYSMICON 标志将分别告诉您“小”图标的 X 和 Y 尺寸。

const int SM_CXICON   = 11;
const int SM_CYICON   = 12;
const int SM_CXSMICON = 49;
const int SM_CYSMICON = 50;

[DllImport("user32.dll")]
static extern int GetSystemMetrics(int smIndex);
static Size GetBigIconSize()
{
    int x = GetSystemMetrics(SM_CXICON);
    int y = GetSystemMetrics(SM_CYICON);
    return Size(x, y);
}
static Size GetSmallIconSize()
{
    int x = GetSystemMetrics(SM_CXSMICON);
    int y = GetSystemMetrics(SM_CYSMICON);
    return Size(x, y);
}

I haven't actually verified this by testing it or looking at the disassembled WinForms code, so I'm not sure if this answer will satisfy the bounty condition of "credible and/or official sources". But I think I'm pretty [in]credible, so I'll give it a shot anyway!

You're setting the icon associated with the window class. You're doing it with the SetClassLong[Ptr] function and GCL_HICON/GCL_HICONSM indices, but it has the same effect as setting it in the WNDCLASSEX structure at the time that the class is registered. This sets the default icon for windows of that class.

However, individual windows can set their own icons, overriding the default icon provided by their class. You do this by sending the WM_SETICON message, passing either ICON_BIG or ICON_SMALL as the wParam and a handle to the icon as the lParam. Presumably, this is what WinForms is doing. That's why the "default" WinForms icon is appearing instead of the default window class icon you're assigning, because WinForms is setting its default icon using WM_SETICON, not via the window class. The only thing "default" about the WinForms icon is that it's assigned automatically by the framework if you don't assign a different custom icon. It doesn't fit any other definition of "default"—certainly not one that might be used from a Win32 perspective.

The Form.Icon property definitely uses WM_SETICON to modify the icon, that's why it is working as expected. Now, you say you don't want to set the Icon property because

I really want to be able to provide two different icons created on the fly, not bind the Form.Icon to an icon on disk. That's why I am trying to use the P/Invoke code to specify the icons in memory.

But that doesn't mean you can't set the Icon property. You can specify a handle to an icon (HICON) here, just as well as you can if you use P/Invoke. All you need is the static Icon.FromHandle method, which creates a new Icon object from the specified HICON. You then assign this Icon object to the form's Icon property.

You don't have to, though. You can use P/Invoke if you want:

const int WM_SETICON = 0x80;

enum IconType
{
    ICON_BIG   = 1;
    ICON_SMALL = 0;
}

[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd,
                                 int message,
                                 IntPtr wParam,
                                 IntPtr lParam);

Then, call it similar to what you have:

IntPtr hIcon32x32 = ...;
IntPtr hIcon16x16 = ...;
SendMessage(this.Handle, WM_SETICON, (IntPtr)IconType.ICON_BIG, hIcon32x32);
SendMessage(this.Handle, WM_SETICON, (IntPtr)IconType.ICON_SMALL, hIcon16x16);

Only one thing you're doing wrong: assuming that the "big" icon will always be 32x32 pixels and that the "small" icon will always be 16x16 pixels. At least, I'm assuming that you're doing this from the names of the variables. If so, that's an invalid assumption. Those are only the most common sizes. They are not guaranteed to be the same in all environments. This is why it's important to provide larger icons in your .ico file; for example, a 48x48 icon. Since you're setting the icons dynamically, Windows won't have access to a larger icon to downsample and you might end up with something really blurry and ugly when your 32x32 icon is scaled up.

To retrieve the actual sizes, call the GetSystemMetrics function. The SM_CXICON and SM_CYICON flags will tell you the X and Y dimensions, respectively, of the "big" icon. The SM_CXSMICON and SM_CYSMICON flags will tell you the X and Y dimensions, respectively, of the "small" icon.

const int SM_CXICON   = 11;
const int SM_CYICON   = 12;
const int SM_CXSMICON = 49;
const int SM_CYSMICON = 50;

[DllImport("user32.dll")]
static extern int GetSystemMetrics(int smIndex);
static Size GetBigIconSize()
{
    int x = GetSystemMetrics(SM_CXICON);
    int y = GetSystemMetrics(SM_CYICON);
    return Size(x, y);
}
static Size GetSmallIconSize()
{
    int x = GetSystemMetrics(SM_CXSMICON);
    int y = GetSystemMetrics(SM_CYSMICON);
    return Size(x, y);
}
撩心不撩汉 2024-08-29 00:03:08

您可以使用Form.Icon。您只需要一个包含 16x16 和 32x32 像素版本图标的图标文件。

我刚刚尝试过,使用包含 32x32 像素红色圆圈和 16x16 蓝色矩形的单个图标文件。小窗口图标显示蓝色矩形,alt-tab 图标显示红色圆圈。

根本不需要 P/Invoke。

You can use Form.Icon. You just need a single icon file that contains 16x16 and 32x32 pixel versions of your icon.

I just tried it, using a single icon file that contains a 32x32 pixel red circle and a 16x16 blue rectangle. The small windows icon shows the blue rectangle, the alt-tab icon shows a red circle.

No need for P/Invoke at all.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文