Tuesday, May 25, 2010

WPF Assigning Dynamic Icon to Image control


When working with WPF applications, display images is a breeze. The Image control provides rich features to display images of various formats like JPEG, PNG, ICO, BMP, GIF etc. Displaying an image is as simple as setting the Image.Source property to the appropriate image file path. No special coding is required to work with different file formats.



Ofcourse you need to worry about other aspects like location of the Image control, its size and also setting the Stretch property to appropriately display the image. I haven't shown all that here for sake of simplicity.

However, this is all fine when working with image files directly. What happens when you don't have a direct file path, but an image in memory like an Icon or Bitmap? If you try to assign say an Icon directly to Image.Source, you get a type cast error stating that conversion from Icon to ImageSource isn't possible. ImageSource incidently is the type that Image.Source property expects.

So how do you get this working. I found some hints at the WPF forum. However this still deals with Icon files that are available as application resources.

I was actually building another example where I wanted to display icon associated with any file type, as displayed in the Windows explorer. I could use the Icon.ExtractAssociatedIcon method to get the required Icon and then display it.

Since I was working on a WPF application, I had to display this icon using the Image control and there is where I landed in trouble. Refering to the above forum question, I tried various options and the simplest of code that worked for me is as below. I am providing the complete code here.

Note that directly using Icon in the code conflicts with Window.Icon property and hence I used the IconImage alias to refer to System.Drawing.Icon class. This also requires adding the reference to System.Drawing assembly.
XAML Code



xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Title="WPFWindowAPP" Height="164" Width="405"

>














Code behind code
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using IconImage=System.Drawing.Icon;
using Microsoft.Win32;
using System.IO;

namespace WPFWindowAPP
{
///
/// Interaction logic for IconLoader.xaml
///

public partial class IconLoader : System.Windows.Window
{

public IconLoader()
{
InitializeComponent();
}

void browseClick(object sender, RoutedEventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.ShowDialog();
filePath.Text = dlg.FileName;
}

void btnClick(object sender, RoutedEventArgs e)
{
IconImage ico = IconImage.ExtractAssociatedIcon(filePath.Text);
MemoryStream strm = new MemoryStream();
ico.Save(strm);
IconBitmapDecoder ibd = new IconBitmapDecoder(strm, BitmapCreateOptions.None, BitmapCacheOption.Default);
icoDisplay.Source = ibd.Frames[0];
}
}
}


You can copy this code to your own WPF Windows application and try it out. Do also note that the .NET method to extract icon returns a large icon. In case you want to get the small icon, you will have to use p/invoke and call Win32 SHGetFileInfo API from Shell32. You can get a sample implementation of this API here.

There is a catch with this code however. If you implement and run it, you will see that the icons loose their color and are displayed as gray scale. Following code fixes this.
void btnClick(object sender, RoutedEventArgs e)
{
IconImage ico = IconImage.ExtractAssociatedIcon(filePath.Text);
Bitmap bmp = ico.ToBitmap();
MemoryStream strm = new MemoryStream();

bmp.Save(strm, System.Drawing.Imaging.ImageFormat.Png);

strm.Seek(0, SeekOrigin.Begin);

PngBitmapDecoder pbd = new PngBitmapDecoder(strm, BitmapCreateOptions.None, BitmapCacheOption.Default);
icoDisplay.Source = pbd.Frames[0];
}


Finally, there is yet another way to display the icon and that uses BitmapImage class as shown in the code below.
void btnClick(object sender, RoutedEventArgs e)
{
IconImage ico = IconImage.ExtractAssociatedIcon(filePath.Text);
Bitmap bmp = ico.ToBitmap();
MemoryStream strm = new MemoryStream();
bmp.Save(strm, System.Drawing.Imaging.ImageFormat.Png);

BitmapImage bmpImage = new BitmapImage();

bmpImage.BeginInit();
strm.Seek(0, SeekOrigin.Begin);
bmpImage.StreamSource = strm;
bmpImage.EndInit();

icoDisplay.Source = bmpImage;
}


Note that you can use the System.Drawing.Imaging.ImageFormat.Jpeg option as well, but you will loose the transparency effect and the icon will be displayed with a black background.

Comments welcome !