Transmitting Images in WCF – Databinding Images in WPF

Introduction

This was an interesting scenario I came across; I had a WPF application consuming a WCF service that was returning information about employees. I needed to add the ability to display a thumbnail photo of the employee. The thumbnails in question were stored alongside the other employee data in an MSSQL 2005 database using an Image column.

The main decision to make was what types to use when shipping the data around. I was using LINQ to pull the data from the database so the thumbnails come out as a System.Data.Linq.Binary and need to end up in a format than can be databound in WPF.

Adding the Thumbnail to WCF Service

One option was to ship the data out of the WCF Service as a byte array but I really wanted the data to be exposed to clients as an image rather than raw data. I opted for System.Drawing.Bitmap as this can be serialised by default, I also wanted to keep the image in a format that is usable by WinForms applications as this service is also consumed by some non WPF applications.

First step was to add the following property to my employee data contract;

[DataMember]
public System.Drawing.Bitmap Photo { get; set; }

Second step was to populate the new property;

if (databaseRow.Photo != null)
{
  System.IO.MemoryStream imgStream =
    new System.IO.MemoryStream(databaseRow.Photo.ToArray());

  System.Drawing.Bitmap img =
    new System.Drawing.Bitmap(imgStream);

  employeeDataContract.Photo = img;
  imgStream.Close();
}

Consuming the Thumbnail in WPF

A WPF Image control won’t bind to a System.Drawing.Image so I added this simple converter to cast the image to a WPF friendly format, notice that I’m implementing the IValueConverter interface.

namespace WPFFrontEnd
{
  ///<summary>
  /// Converts System.Drawing.Bitmap to BitmapSource
  ///</summary>
  public class CustomImageConverter : IValueConverter
  {
    ///<summary>
    /// Converts System.Drawing.Bitmap to BitmapSource
    ///</summary>

    public object Convert(object value, Type targetType, object parameter,
      System.Globalization.CultureInfo culture)
    {
      //No conversion to be done if value is null
      if (value == null) return null;

      //Validate object being converted
      if (value is System.Drawing.Bitmap)
      {
        //Use existing Interop functionality to perform conversion
        IntPtr HBitmap = ((System.Drawing.Bitmap)value).GetHbitmap();

        System.Windows.Media.Imaging.BitmapSizeOptions sizeOptions =
          System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions();

        return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
          HBitmap, IntPtr.Zero, Int32Rect.Empty, sizeOptions);
       }
       else
       {
         //We don't want the conversion to fail if it's not valid
         return null;
       }
     }

     ///<summary>
     /// NOT IMPLEMENTED
     ///</summary>
     ///<returns></returns>
     public object ConvertBack(object value, Type targetType,
       object parameter, System.Globalization.CultureInfo culture)
     {
       throw new NotImplementedException();
     }
   }
}

Now the only thing left to do is display the image, I’m using a DataTemplate to display employees so I’ve added an instance of our converter to the DataTemplate.Resources and specified the use of this converter when databinding the image. Note that I have imported the namespace of the application (WPFFrontEnd in my case) with a prefix if local in the top ResourceDictionary tag.

<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFFrontEnd">

<DataTemplate x:Key="employeeListTemplate">
    <DataTemplate.Resources>
        <local:CustomImageConverter x:Key="ImgConv" />
    </DataTemplate.Resources>

   <WrapPanel>
      <Image Source="{Binding Path=Photo, Converter={StaticResource ImgConv}}" />
   </WrapPanel>
</DataTemplate>

</ResourceDictionary>

Other Thoughts

I did also experiment with transmitting the thumbnails from the WCF Service in a WPF friendly image format to remove the need for the converter but it doesn’t appear that the WPF based image formats will serialise out of the box.

I’ll leave you with the code I used when writing the image property of an EmployeeDataContract back into the database;

//Save the image data to a stream
System.IO.MemoryStream imageStream = new System.IO.MemoryStream();
employee.Photo.Save(imageStream, System.Drawing.Imaging.ImageFormat.Jpeg);
imageStream.Position = 0;

//Write the stream back out to raw binary for the db
byte[] data = new byte[imageStream.Length];
imageStream.Read(data, 0, (int)imageStream.Length);
employeeRowToUpdate.Photo = data;
Advertisements