SetClassLong(hWnd, GCL_HICON, hIcon) cannot replace WinForms Form.Icon

£可爱£侵袭症+ 提交于 2019-12-06 12:47:47

问题


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.


回答1:


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);
}



回答2:


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.



来源:https://stackoverflow.com/questions/2266479/setclasslonghwnd-gcl-hicon-hicon-cannot-replace-winforms-form-icon

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!