Using Win32 Icons in WPF

code Windows WPF

Using custom icons can be a little tricky in WPF. It's simple enough if you want to use your application's main icon or an icon file that you can refer to using a pack URI - so long as you do that, everything just works.

However, if your icon data is anywhere else, then things can get a little tricky.

The Icon Property

It seems enough to just set a window's Icon property to any old ImageSource should be enough, and indeed that generally works.

However there's a snag. An ImageSource typically refers to just one image, whereas Windows requires two separate images. These images have different sizes, according to the current system metrics. The larger one needs to be SM_CXICON by SM_CYICON pixels, and is used in the task-switcher dialog and on the Windows 7 task bar. The smaller one is SM_CXSMICON by SM_CYSMICON, and is used in the window's caption and on the task bar (in the preview thumbnails that pop up on Windows 7).

If you set the window's icon to a simple bitmap image, then WPF will simply scale it to the two sizes and pass those images to Windows. Unfortunately, images which work well at one size (usually 32 by 32) tend to look bad at the other (16 by 16). That's why Windows icon files have individually authored images for each size - the two images will be different, each created specifically with its size in mind. We can't do that by just throwing any old ImageSource at the Icon property.

And yet everything works fine if we set the property to a URI that refers to a windows icon file - the system will happily find the correct image in the icon data. So what does WPF do with that URI and how do we replicate it if we haven't got our image data in a URI-friendly location?

The BitmapFrame Class

The trick is that when WPF decodes an icon, it returns a BitmapFrame object. That object keeps a reference back to the decoder which parsed the icon file. When you set the Icon property to a BitmapFrame, WPF will go and look at the frame's decoder's output and see if that decoder found more than a single image in the source file. If it did, WPF will choose the two images from that set which best match the required resolution and color depth, scale those if they're not exact matches, and then pass those images to Windows.

So all we need to do is decode a multi-image file and pass one of the resulting images to the Icon property, and WPF will do the rest.

Loading a Windows Icon From a Stream

The typical multi-image file format that's used for Windows icons is, unsurprisingly, the Windows Icon (.ico) format. Loading one of these is trivial. All you need to do is get your data into a Stream, and you can pass it to IconBitmapDecoder's constructor. Once the decoder is constructed, simply set the target window's Icon property to any one of the frames that the decoder loaded from the file:

Stream icoData = //load the data from wherever it is
var ico = new IconBitmapDecoder( icoData, BitmapCreateOptions.None,
    BitmapCacheOption.Default );
window.Icon = ico.Frames[0];

Loading From a Windows Resource

One of the more common places to find icon resources is embedded into PE (executable) files. Loading icon resources from these is a little tricky, since the icon's parts are split up into multiple resource entries, and IconBitmapDecoder can't handle that directly.

Fortunately, we know to fix that. We simply load the icon resource into a MemoryStream using that code and pass the stream to IconBitmapDecoder.