In this post I demonstrate a method for binding a Silverlight 3 DataGrid to dynamic data, i.e. data which does not have properties that are known at design time. This technique results in a bound grid which is sortable and editable. This blog post is a bug fix (due to differences between SL2 and SL3) and expansion on my previous posts on this subject.

WinForms, WPF and ASP.NET are all perfectly capable of binding to dynamic data via a DataGrid, or custom TypeDescriptors. However Silverlight has neither of these features. Around one year ago I published a pair of articles that demonstrated a technique that could be used to bind a DataGrid to dynamic data presented as a list of dictionaries. The first article detailed how to use a ValueConverter to access the cell values within a dictionary and how a custom CollectionView could be created to permit sorting. The second article showed how to extend this solution to enable editing within the grid.

These two blog posts have proven very popular, with 84 comments between them :-). However I have seen a recurring theme in the comments to these posts which I will now address:

  1. SL3 Sorting - I have had a number of reports that indicate sorting is broken in Silverlight 3
  2. SL3 Editing - It looks like editing is also broken in SL3 :-(
  3. Adding columns in code behind - My examples configured columns in XAML, but for truly dynamic data this would have to be done in code-behind. A number of readers have had difficulty with converting the XAML into the required C# code.

This blog post will address these specific issues, providing a solution that works for SL3. If you are interested in the technical solution you might want to read the first and second blog posts before you read this one. The solution for SL3 is essentially the same, it is just a few subtle differences in the DataGrid that cause these issues.

Starting with the first of the SL3 problems, sorting. The DataGrid uses the SortDescriptions property of our collection which implements ICollectionView in order to sort the data. This remains unchanged in SL3. However, the ICollectionView implementation that I presented in the previous blog post did not implement all the methods on this interface, leaving out the ones that the SL2 DataGrid does not use.

When the SL3 DataGrid performs a sort or group operation, it first calls the DeferRefresh method on ICollectionView. This is quite a neat little method; what it does is allow you to suppress the events that the collection would typically raise whilst you make a number of changes, for example, applying a sort then a grouping, then raises a single collection changed event. This results in much less work being performed by the UI as it now handles a single event rather than multiple events. You can find a good example of how this works on Matt Manela's blog. DeferRefresh is implemented by returning in IDisposable object, the implementation is quite trivial:

public class SortableCollectionDeferRefresh : IDisposable
{
    private readonly SortableCollectionView _collectionView;

    internal SortableCollectionDeferRefresh(SortableCollectionView collectionView)
    {
        _collectionView = collectionView;
    }

    public void Dispose()
    {
        // refresh the collection when disposed.
        _collectionView.Refresh();
    }
}

It is used by our collection as follows:

public class SortableCollectionView : ObservableCollection<Row>, ICollectionView
{
  ...
  public IDisposable DeferRefresh()
  {
      return new SortableCollectionDeferRefresh(this);
  }
  ...
}

That solves the sorting issue :-)

The lack of editing issue was a but odd, someone on the Silverlight forums indicated that they think this is an undocumented breaking change. With a SL3 DataGrid if you bind to a property of type object, the column becomes read-only, even if the DataGrid itself is not read-only. The solution is simply to set the IsReadOnly of each column to false.

With these few changes we now how a fully functioning DataGrid bound to our dynamic data:

The final recurring question to my previous blog posts is how to create the bound DataGrid columns in code-behind. To illustrate how this is done I will create an example where the DataGrid is bound to some XML that sits in a TextBox underneath the grid.

The example looks like this:

The two buttons in the centre allow you to synchronise the DataGrid and the XML, one formats the current grid contents in XML, the other takes the XML and dynamically binds the contents to the grid. You can try editing the data, then updating the XML and vice-versa. You can even add new columns to the XML data (hopefully the XML structure is pretty self explanatory - there is no error checking so take care ;-)). The DataGrid is of course editable and sortable.

The interesting part of the code is the method that takes the XML contents and binds it to the grid. It is as follows:

/// <summary>
/// Copies the XML contents of the textbox into the DataGrid
/// </summary>
private void XmlToGrid()
{
  // clear the grid
  _dataGrid.ItemsSource = null;
  _dataGrid.Columns.Clear();

  // grab the xml into a XDocument
  XDocument xmlDoc = XDocument.Parse(_xmlInput.Text);

  // find the columns
  List<string> columnNames = xmlDoc.Descendants("column")
                                   .Attributes("name")
                                   .Select(a => a.Value)
                                   .ToList();

  // add them to the grid
  foreach (string columnName in columnNames)
  {
    _dataGrid.Columns.Add(CreateColumn(columnName));
  }

  SortableCollectionView data = new SortableCollectionView();

  // add the rows
  var rows = xmlDoc.Descendants("row");
  foreach (var row in rows)
  {
    Row rowData = new Row();
    int index = 0;
    var cells = row.Descendants("cell");
    foreach(var cell in cells)
    {
      rowData[columnNames[index]] = cell.Value;
      index++;
    }
    data.Add(rowData);
  }

  _dataGrid.ItemsSource = data;
}

The above code clears the grid, then uses a bit of Linq to XML to query the XML within the TextBox, creating the SortableCollectionView and Row instances which are the data objects for our dynamic data as described in the previous blog posts. The columns are created in code behind as follows:

private RowIndexConverter _rowIndexConverter = new RowIndexConverter();

private DataGridColumn CreateColumn(string property)
{
  return new DataGridTextColumn()
  {
    CanUserSort = true,
    Header = property,
    SortMemberPath = property,
    IsReadOnly = false,
    Binding = new Binding("Data")
    {
      Converter = _rowIndexConverter,
      ConverterParameter = property
    }
  };
}

This is really no different to the technique that you use when creating the column definitions in XAML. There is nothing special about XAML, it is essentially just an XML markup for creating objects.

Hopefully this blog post will help answer the recurring questions, and reduce the number of "it doesn't work in SL3" mails I get. Perhaps I will just get 84 "Thank you" comments instead :-)

You can download the full sourcecode for this blog post: SilverlightTable.zip

Regards, Colin E.