Aymeric on Software

Because we needed another of these blogs...

How to Use WM_SETICON Properly

It’s not rocket science, but you’d be surprised to see how many people get this wrong. Here’s a code snippet of what people usually do:

1
2
3
4
5
6
7
8
9
const HICON hicon = ::LoadIcon(
	::GetModuleHandle(0),
	MAKEINTRESOURCE(IDR_MAINFRAME)
);
if (hicon)
{
  ::SendMessage(hwnd, WM_SETICON, ICON_BIG, reinterpret_cast(hicon));
  ::SendMessage(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast(hicon));
}

This code would set the large icon (as displayed in the ALT-TAB switch window dialog) and the small icon (as displayed in the window title bar or the task bar) of the window (whose handle is hwnd).

Except for the error checking which is not particularly thorough, most people would consider this code to be correct. You could also have used LoadImage with LR_DEFAULTSIZE.

The problem reveals itself with an icon which has a sligthly different design for the small 16×16 size compared to the large 32×32 size. The 16×16 icon will look like a scaled down version of the 32×32 one. The reason why the smaller icon looks different in the first place is usually because the large icon’s design is a bit overloaded and cannot be downscaled without becoming unrecognizable; so you will notice!

What’s wrong? If you dig into the documentation you will found the following information:

  • LoadIcon loads the most appropriate icon, at one fixed size, which is SM_CXICON by SM_CYICON pixels, ie the size of a large icon.
  • The small icon should be SM_CXSMICON by SM_CYSMICON pixels.
  • WM_SETICON for ICON_SMALL would downscale a larger icon to SM_CXSMICON by SM_CYSMICON pixels automatically.
  • This odd behavior is due to backward compatibility with legacy versions of windows, which only had one size of icons.

Therefore the fix is easy, all you need to do is call LoadImage twice and explicitly provide the size of the icon.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const HANDLE hbicon = ::LoadImage(
  ::GetModuleHandle(0),
  MAKEINTRESOURCE(IDR_MAINFRAME),
  IMAGE_ICON,
  ::GetSystemMetrics(SM_CXICON),
  ::GetSystemMetrics(SM_CYICON),
  0);
if (hbicon)
  ::SendMessage(hwnd, WM_SETICON, ICON_BIG, reinterpret_cast(hbicon));

const HANDLE hsicon = ::LoadImage(
  ::GetModuleHandle(0),
  MAKEINTRESOURCE(IDR_MAINFRAME),
  IMAGE_ICON,
  ::GetSystemMetrics(SM_CXSMICON),
  ::GetSystemMetrics(SM_CYSMICON),
  0);
if (hsicon)
  ::SendMessage(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast(hsicon));

Reference (MSDN):