I’ve been working with Silverlight DataGrids so I can learn about their strengths and weaknesses. And I ran across a situation in which I needed to do some custom data binding to populate cells in a DataGrid template column. I suspect other people will encounter similar situations, so I wanted to document the problem—and the solution—here.

The specific need that I encountered involved a DataGrid that was was bound to data coming from an ADO.NET data service. The DataGrid definition looked something like this:

<data:DataGrid x:Name=”Output” Width=”600″ AutoGenerateColumns=”False”>

  <data:DataGrid.Columns>

    <data:DataGridTextColumn Width=”96″ Binding=”{Binding Title}” Header=”Title” />

    <data:DataGridTextColumn Width=”64″ Binding=”{Binding Number}” Header=”Number” />

    <data:DataGridTextColumn Width=”64″ Binding=”{Binding Year}” Header=”Year” />

    <data:DataGridTextColumn Width=”64″ Binding=”{Binding Rating}” Header=”Grade” />

    <data:DataGridCheckBoxColumn Width=”64″ Binding=”{Binding CGC}” Header=”CGC?” />

    <data:DataGridTemplateColumn Width=”128″ Header=”Cover”>

    <data:DataGridTemplateColumn.CellTemplate>

      <DataTemplate>

        <Image Width=”64″ Margin=”4″ />

      </DataTemplate>

    </data:DataGridTemplateColumn.CellTemplate>

    </data:DataGridTemplateColumn>

  </data:DataGrid.Columns>

</data:DataGrid>

 

The problem was the template column at the bottom, which was intended to display thumbnail images of the items in the DataGrid rows. The image bits were part of the data in the objects retrieved from the data service, but there was no obvious way to connect the XAML Images in the template column to the image data stored in the Thumbnail property of the objects bound to the DataGrid rows. In other words, I couldn’t do this:

 

<Image Width=”64″ Margin=”4″ Source=“{Binding Thumbnail}” />

 

This syntax would work if Thumbnail contained an image URI, but it doesn’t work at all when Thumbnail contains the actual image bits. So how does one bind a XAML image to actual image bits retrieved from a data service?

 

My first solution was to process the DataGrid’s LoadingRow events and manually data-bind the image bits stored in the Thumbnail property of the data source to the XAML Image object in the current row of the DataGrid. Here’s what the event handler looked like:

 

private void Output_LoadingRow(object sender, DataGridRowEventArgs e)

{

    Image image = (Image)Output.Columns[5].GetCellContent(e.Row);

    Byte[] bits = ((Comics)e.Row.DataContext).Thumbnail;

 

    if (image != null && bits != null && bits.Length > 0)

    {

        BitmapImage bi = new BitmapImage();

        bi.SetSource(new MemoryStream(bits));

        image.Source = bi;

    }

}

 

The first statement initializes a reference to the Image in the row’s template column. The second statement initializes a reference to the image bits in the Thumbnail property of the corresponding object in the data source. The final few statements stuff the image bits into a BitmapImage and then assign the BitmapImage to the Image. It worked like a charm, and I learned a few things about DataGrid that I didn’t know before (such as how to get a reference to the contents of a specific cell in a row inside a LoadingRow event handler).

 

I didn’t like this solution because the code is rather awkward. For one thing, if the layout of the DataGrid changes, the code might have to change, too. (Notice the column index passed in the first line.) So I decided to rewrite the code to use a value converter, not knowing whether it would work or not. The big question: would Silverlight accept a value for an Image object’s Source property if the value converter returned a BitmapImage rather than a string? I’m happy to report that the answer is yes, and it allowed me to clean up the syntax considerably.

 

In the modified code, I first implemented a value converter like this:

 

public class ImageConverter : IValueConverter

{

    public object Convert(object value, Type targetType,

        object parameter, System.Globalization.CultureInfo culture)

    {

        BitmapImage bi = new BitmapImage();

        bi.SetSource(new MemoryStream((Byte[])value));

        return bi;

    }

 

    public object ConvertBack(object value, Type targetType,

        object parameter, System.Globalization.CultureInfo culture)

    {

        throw new NotImplementedException();

    }

} 

 

Next, I declared an instance of the value converter class as a XAML resource like this:

 

<Grid.Resources>

  <udt:ImageConverter x:Key=”ImageConverter” />

</Grid.Resources>

 

Finally, I modified the template column so that it was defined like this:

 

<data:DataGridTemplateColumn Width=”128″ Header=”Cover”>

  <data:DataGridTemplateColumn.CellTemplate>

    <DataTemplate>

      <Image Width=”64″ Margin=”4″ Source=”{Binding Thumbnail, Converter={StaticResource ImageConverter}}” />

    </DataTemplate>

  </data:DataGridTemplateColumn.CellTemplate>

</data:DataGridTemplateColumn>

 

<

p class=”MsoNormal”>This did away with the need for the LoadingRow event handler, and produced code that is less fragile, easier to understand, and more maintainable. That’s a win all the way around—and a cool use for Silverlight value converters that’s a sure-fire ice breaker at the next Silverlight party you attend.