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

时间:2010-02-15 14:19:00

标签: winforms winapi pinvoke icons

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

我发布了一个related question,它提出了一个非常简单的解决方案,适用于本机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默认图标。
  3. ...但是,有趣的是,当我按下Alt-Tab时,我会在很短的时间内看到我使用GCL_HICON定义的图标(如果我不使用GCL_HICONSM GCL_HICON)。似乎在幕后发生了一些事情,迫使Windows使用WinForms默认图标绘制图标。

    我无法弄清楚我做错了什么以及幕后发生了什么。

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

2 个答案:

答案 0 :(得分:3)

我实际上并没有通过测试或查看反汇编的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方法,它从指定的Icon创建一个新的HICON对象。然后,将此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像素而“小”图标总是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);
}

答案 1 :(得分:2)

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

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

根本不需要P / Invoke。