How to use WM_SETICON Properly

How NOT to use WM_SETICON

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:

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

The code above 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.

Why this is wrong

The problem reveals itself with an icon which has a slightly 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, i.e. 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.

How to use WM_SETICON

Now that we understand the problem (the wrong size of the icon is loaded), we know how to fix it. The solution is to call LoadImage twice and explicitly provides the size of the icon to avoid the downscaling.

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

References

Links to MSDN: