Custom Data Binding to Cells in Silverlight DataGrid Template Columns (and a Novel Use for Value Converters)

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.

Jeff Prosise

View Comments

  • Nice one, Jeff.
    This certainly beats building an .aspx page to serve out images from the database.
    Greets,
    Jonathan

  • hi jeff.
    i need to show in a datagrid column a TimeLineMarker.
    I've create a TimeLineMarkerConverter class anche this is the column definition:
    all the others columns show exactly texts and numbers, but in this case the datagrid column shows "System.Windows.Media.TimeLineMarker".
    do i have to do more to convert the TimeLineMarker?
    the TimeLineMarker class has these methods:
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
    TimelineMarker nullableTimeSpan = null; ;
    if (value == null)
    return string.Empty;
    if (value is TimelineMarker)
    { nullableTimeSpan = (TimelineMarker)value;
    if (nullableTimeSpan == null)
    return string.Empty;
    }
    return nullableTimeSpan.Time.Days + ":" + nullableTimeSpan.Time.Hours + ":" + nullableTimeSpan.Time.Minutes + ":" + nullableTimeSpan.Time.Seconds + ":" + nullableTimeSpan.Time.Milliseconds;
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
    string[] v = ((string)value).Split(':');
    TimeSpan ts = new TimeSpan(int.Parse(v[0]),
    int.Parse(v[1]), int.Parse(v[2]), int.Parse(v[3]), int.Parse(v[4]));
    TimelineMarker t = new TimelineMarker();
    t.Time=ts;
    return t;
    }
    thanks in advance,
    Federica

  • Error when setup databinding with image converter for listbox using datatemplate
    at MS.Internal.XcpImports.CheckHResult(UInt32 hr)
    at MS.Internal.XcpImports.BitmapSource_SetSource(BitmapSource bitmapSource, CValue& byteStream)
    at System.Windows.Media.Imaging.BitmapSource.SetSourceInternal(Stream streamSource)
    at System.Windows.Media.Imaging.BitmapImage.SetSourceInternal(Stream streamSource)
    at System.Windows.Media.Imaging.BitmapSource.SetSource(Stream streamSource)
    at display_db_image.ImageConverter.Convert(Object value, Type targetType, Object parameter, CultureInfo culture)
    at System.Windows.Data.BindingExpression.ConvertToTarget(Object value)
    at System.Windows.Data.BindingExpression.GetValue(DependencyObject d, DependencyProperty dp)
    at System.Windows.DependencyObject.RefreshExpression(DependencyProperty dp)
    at System.Windows.Data.BindingExpression.RefreshExpression()
    at System.Windows.Data.BindingExpression.SendDataToTarget()
    at System.Windows.Data.BindingExpression.SourceAquired()
    at System.Windows.Data.BindingExpression.DataContextChanged(Object o, DataContextChangedEventArgs e)
    at System.Windows.FrameworkElement.OnDataContextChanged(DataContextChangedEventArgs e)
    at System.Windows.FrameworkElement.OnAncestorDataContextChanged(DataContextChangedEventArgs e)
    at System.Windows.FrameworkElement.NotifyDataContextChanged(DataContextChangedEventArgs e)
    at System.Windows.FrameworkElement.OnTreeParentUpdated(DependencyObject newParent, Boolean bIsNewParentAlive)
    at System.Windows.DependencyObject.UpdateTreeParent(IManagedPeer oldParent, IManagedPeer newParent, Boolean bIsNewParentAlive, Boolean keepReferenceToParent)
    The same error also reported from this feed
    http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=436047
    I am using vs2008 and silverlight 3 tookit RTM version.
    Any suggestion?

  • Jeff, I'm new to the Silverlight. In your solution, you mention
    "ext, I declared an instance of the value converter class as a XAML resource like this:
    "
    Can you explain the details of how to do this.? I created a resource xaml file and I'm being warned udt does not exist and Grid.Resources cannot be found. I realize I need to add the xaml resource file I create to the App.xaml node, but I'm not sure what the file it references needs to reference , also if you have a recommendation of where in the project to store the ImageConverter class. Thank you!!!

  • ...actually I think I need an ice breaker if someone invites me to a "Silverlight Party".
    Thanks for the blog post, it was helpful

  • Could someone translate it into vb.net ?
    especially :
    bi.SetSource(new MemoryStream((Byte[])value));
    please

  • Thanks for the explanation Jeff, much appreciated. However, when I run the code, I get the following error:
    Unable to cast object of type 'Binary' to type 'System.Byte[]'
    This error occurs on the line:
    bi.SetSource(new MemoryStream((Byte[])value));
    Can you recommend a solution?
    Thanks! Rob

Recent Posts

8-Step AWS to Microsoft Azure Migration Strategy

Microsoft Azure and Amazon Web Services (AWS) are two of the most popular cloud platforms.…

2 weeks ago

How to Navigate Azure Governance

 Cloud management is difficult to do manually, especially if you work with multiple cloud…

3 weeks ago

Why Azure’s Scalability is Your Key to Business Growth & Efficiency

Azure’s scalable infrastructure is often cited as one of the primary reasons why it's the…

1 month ago

Unlocking the Power of AI in your Software Development Life Cycle (SDLC)

https://www.youtube.com/watch?v=wDzCN0d8SeA Watch our "Unlocking the Power of AI in your Software Development Life Cycle (SDLC)"…

2 months ago

The Role of FinOps in Accelerating Business Innovation

FinOps is a strategic approach to managing cloud costs. It combines financial management best practices…

2 months ago

Azure Kubernetes Security Best Practices

Using Kubernetes with Azure combines the power of Kubernetes container orchestration and the cloud capabilities…

2 months ago