Wednesday, August 01, 2007

WPF - Point an Image to an Embedded Resource

Occasionally, you may need to load a bitmap image into an Image element within WPF. Normally, this isn’t too difficult. However, it can be tricky to load the image if it is an embedded resource within your assembly. That is, it can be tricky until you do it just once.

This type of situation may arise if you are attempting to distribute an application using ClickOnce technology. ClickOnce is really sweet and I think it is incredibly valuable from the perspective of a user. I believe a lot of users are intimidated or annoyed by the traditional approach of:
  • Downloading a file and saving somewhere locally, like the Desktop
  • Double-clicking the new icon on the desktop
  • Selecting “Run” from the “Open File –S ecurity Warning” dialog
  • Going through a wizard which includes:
    • A license agreement that nobody reads. I’m not saying they shouldn’t read it (they should); I’m simply saying nobody does
    • A prompt to determine where the application should be installed, which the majority of users select the default anyway.
    • A summary step that essentially says “once you click “Next/Finish” your application will be installed”.
As you can see from this list, the majority of users really only care about the last nested bullet point: installing the application. So, once we have installed the application, how do we programmatically get those images that are bundled along with the deployment? Here are three steps to guide you:
  1. Retrieve the image from the assembly. Bear in mind that by using the “embedded resource” build action on an image, it gets bundled up with the deployed file. In order to access this file we can use the following code:
    System.IO.Stream stream = this.GetType().Assembly.GetManifestResourceStream(“[namespace].[imageFileName].[extension]”);
    Please take note of the “[namespace]” prefix. This value will be RootNamespace associated with your C# project.
  2. Put on your decoder ring. The Source property of an Image in WPF is an ImageSource object. Because of this, we need to get from the Stream retrieved in step 1 to an ImageSource. WPF provides the following decoders to help you convert the stream to a BitmapImage.
  3. As you can probably guess, each decoder is associated with a specific type of bitmap file, for instance .bmp, .gif, .ico, .jpg, .png, .tif, .wmp, etc. files. For the following code sample, we will pretend we are trying to retrieve an embedded .png (portable network graphics) file from our Assembly.
    PngBitmapDecoder bitmapDecoder = new PngBitmapDecoder(fileStream,
    BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);

    From the previous code snippet, you can see that we:
    • Use the PngBitmapDecoder (ring) to decode the Stream we retrieved in step 1.
    • The second parameter in the constructor specifies how the image will be initialized.
    • The third parameter in the constructor specifies how the image will be cached.
  4. Retrieve and Set the Image element.Once decoded, the actual image is stored within the first element of the Frames property of a BitmapDecoder. The reason why the BitmapDecoder exposes a Frames property is because certain files types, such as TIFF and GIF files, support multiple frames (think of an animated .gif). The following snippet shows what this looks like in code:
    ImageSource imageSource = bitmapDecoder.Frames[0];
    testImage.Source = imageSource;

That's It! The complete example looks like this:
System.IO.Stream fileStream = this.GetType().Assembly.GetManifestResourceStream("ClickOnce.image.png");
if (fileStream != null)
{
PngBitmapDecoder bitmapDecoder = new PngBitmapDecoder(fileStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
ImageSource imageSource = bitmapDecoder.Frames[0];
testImage.Source = imageSource;
}
else
{
MessageBox.Show("The file was not found!");
}

We can take off our decoder rings. I know my Silverlight examples have not been converted to be RC1 compliant yet. I am having some more problems with my file hosting provider. I hope to have everything resolved by this weekend. I apologize for any inconveniances.

3 comments:

DSD-SICO said...

Thanks for the article, I was wondering why my Silverlight application wasn't showing images after linking it into an ASP.NET application. In the xaml files I reference the images using relative paths, but after reading your article I now understand that the Source properties aren't used to load the embedded images; the are simply used as relative paths, so I need to pcopy the images to the ASP.NET application as well.

Maybe there is an easier way to use embedded resources inside XAML? Maybe something like:

<ImageBrush ImageSource="resource://namespace.resourcename.png"/>

Anonymous said...

You little beauty! I've been fiddling trying to do this in WPF via C# all week!

Thanks a lot

Bassem said...

Thanks a lot Chad!