Binding a Silverlight 3 DataGrid to dynamic data via IDictionary (Updated)

March 26th, 2010 by Colin Eberhardt

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.

Tags: , ,

18 Responses to “Binding a Silverlight 3 DataGrid to dynamic data via IDictionary (Updated)”

  1. Lukas says:

    I tried to use a DataGridTemplateColumn but had problems. I created a post here:
    http://forums.silverlight.net/forums/p/197273/459400.aspx#459400

  2. Adrian Hara says:

    Great work Colin, love it.

    However, I just wanted to point out, in case it might help somebody, SL4 supports property binding syntax using indexers of type string. So basically you can just create the binding with path “[ColumName]” and it will work. The SortableCollectionView is still needed though ;)

    Cheers,
    Adi

  3. Lukas says:

    Thanks for posting! Your solution helped already a lot!
    Now I try to use a DataGridTemplateColumn instead of a DataGridTextColumn. But I was not able to set the Binding in the content of the template correctly. Do you have any ideas? I would be very thankful if you are able to help.
    Thanks!
    Lukas

  4. latha says:

    Hi,
    I ma using PagedCollectionView with datapager and it is breaking sorting property that is exisitng before, how can I achieve paging in your sample.

  5. Ajith says:

    If the Age values are 1,234 and 5,the sorting is not working as expected as its doing the string sorting…..

  6. Iftah says:

    in case anyone is interested, I fixed the code to handle filters also –
    I added a member List called “filteredItems”
    at the beginning of the Refresh function I put the code:

    var rows = new List(this);
    rows.AddRange(filteredItems); // add the previously filtered items

    if (_filter == null)
    {
    filteredItems.Clear();
    }
    else
    {
    filteredItems = rows.Where(r => _filter(r) == false).ToList();
    rows = rows.Where(r => _filter(r)).ToList();
    }

  7. Iftah says:

    great control! thanks!

    I’m a WPF newbie, and trying to combine your ideas with autofilter listview (http://www.codeproject.com/KB/WPF/AutoFilterListView.aspx),

    I’m having trouble applying the filter in the SortableCollection…
    Can you give some tips how it should be done? (or add auto filter to your control…)

    • Hi,

      The current SortableCollectionView does not make use of the supplied filters within the Refresh() method. I think the addition of a simple Where clause at this point would do the trick.

      Regards, Colin E.

  8. Maul says:

    Hi, very good article and solution, but I believe I found some bug with it, since I’m still new to many things xaml and c#4.0 I couldn’t fix it, maybe you know how to, using your own Page.xaml on the SilverlightTable.zip, cut your loop on line 33 on Page.xaml.cs from 200 to 2 so only 2 rows are generated, then modify line 39 to this : row["Shoesize"] = i;

    Then run the application, you will notice that when you sort the line using the shoesize column in descending order, you will no longer be able to switch the selected row, very odd.

  9. Jeremy says:

    Very nice! Thanks for posting this.

  10. kakamatyi says:

    Colin, great job!

    If you make string sort with integer data (1,2,11) the result will be: 1,11,2
    I would need the result (1,2,11) and perform a custom integer sort…
    Could you please give me any hint how to implement custom sorting?

  11. @Fallon,

    No problem – thanks for your feedback.

    Colin E.

  12. Fallon Massey says:

    Colin, forget that question.

    I went back and looked at part 1, and the explaination was there.

  13. Fallon Massey says:

    Colin, firstly, thanks for the work.

    This may have been asked before, but what is the difference between your solution and the one put forth by Vladimir Bodurov.

    I know his uses a bit of IL Code to create an object to bind to the datagrid, but other than that, do you see any advantages/disadvantages of either approach?

    Thanks.

  14. Binding a Silverlight 3 DataGrid to dynamic data via IDictionary (Updated) | Colin Eberhardt’s Adventures in WPF…

    Thank you for submitting this cool story – Trackback from DotNetShoutout…

Leave a Reply