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: , ,

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

  1. John Reid says:

    Thank you for this great example of how to bind to a Dictionary, it might be a few years old but it is still useful. What is really great is that you explain it all and do not post an incomplete working example.

  2. neel_09 says:

    Hi,
    Thanks for this excellent post. this is exactly what I needed. However I have a question, once I get the xml to datagrid, i want to show the selected item of the grid into a dataform. I have tried to set the Itemsource of my dataform in XMLtoGrid() method, and also tried to set dataform.currentItem = datagrid.selectedItem to bind dataform to the current value, but it is not working. Can you tell me what I am doing wrong here?
    thanks again.

  3. CS says:

    Great job!! I just implemented your solution and it worked great. I appreciate you posting this here. There is no way I would have figured that out on my own and I’m currently on a TIGHT deadline (aren’t we all). Thanks again.

  4. Ashura says:

    Thank you very much, the XML example was very helpful ! Great article.

  5. Leah Lerner says:

    Can I get sample of custom grouping, (like your custom sorting)?

    • Sorry – I don;t have time to include grouping in this example.

    • Micah says:

      You can do grouping using a PagedCollectionView using the binding property indexers

      I just do something like this..
      _pcv.GroupDescriptions.Add(new PropertyGroupDescription(“Data[My Bound Property Name]“)); (for example)

      my dynamic columns creator looks like this.
      private DataGridColumn CreateColumn(string property)
      {
      return new DataGridTextColumn()
      {
      CanUserSort = true,
      Header = property,
      SortMemberPath = String.Format(“Data[{0}]“,property),
      //IsReadOnly = true,
      Binding = new Binding(String.Format(“Data[{0}]“,property))
      };
      }

  6. Matt says:

    Hey Colin,

    here you have one of your 84 “Thank you” ;)
    It helps me a lot!

  7. FG says:

    Great post, helped me a lot!

    As latha, I also tried to use your solution together with PagedCollectionView to get paging and sorting. I finally worked out a solution and post it here, since I did not find anything similar on the internet.

    My solution constists in wrapping the SortableCollectionView into a PagedCollectionView:

    SortableCollectionView data = new SortableCollectionView();
    foreach (var row in {some data source})
    data.Add(row);
    PagedCollectionView Data = new PagedCollectionView(data); // this is the object that the DataGrid and DataPager have to bind to

    and listen to the PropertyChanged event of the PagedCollectionView to pass the SortDescriptions to the SortableCollectionView:

    Data.PropertyChanged += new PropertyChangedEventHandler(Data_PropertyChanged);

    void Data_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
    if (e.PropertyName == “SortDescriptions”)
    {
    var pcv = (PagedCollectionView)sender;
    SortableCollectionView scv = (SortableCollectionView)pcv.SourceCollection;
    scv.SortDescriptions.Clear();
    foreach (var s in pcv.SortDescriptions)
    scv.SortDescriptions.Add(s); // scv.Refresh() is automatically called when SortDescriptions are changed
    }
    }

  8. Dittrich Schobenhauer says:

    Colin,
    Thanks for this solution, it works great in my solution.
    One little question, I tried to get datagrid grouping to work, also looked into the toolkit source how this is done in pagedcollectionview, but can’t get it to work. Has anyone got this working with this solution or for that matter in any solution without using pagedcollectionview?

  9. David Gee says:

    Thanks for this great article, it has been very helpful. I’m in the process of creating a silverlight datagrid application that needs to be used for multiple grids in our application, so the data structure needs to be very flexible. I’ve managed to add template columns to your example, and I also extended the example to have a RowDetails template which contains tabs which contain subgrids. I have a couple of problems, though, which I think are related to SortableCollectionView. I am a front-end javascript/css/html coder who is just getting started with Silverlight, so I’m a bit stuck as I don’t know how to extend your SortableCollectionView example. These are the problems I’m having:

    1. I’m using a TemplateColumn with a dropdown arrow image to toggle the RowDetails on and off, I turned off VisibleWhenSelected. I need to do this to keep the grid in line with our HCI standards. When I sort any of the columns in the grid, the RowDetails collapses and the dropdown image gets stuck in the wrong state. I would ideally like for all of the currently opened RowDetails to stay visible after sorting, but if this isn’t possible, I’d like to at least figure out how to toggle the dropdown image so the state indicator is kept in sync.

    2. I added the DataPager control to this grid, and while it handles breaking the grid into pages well enough, as soon as I add it, I can no longer sort the grid, click on my combobox cells, or expand the RowDetails. Any idea what would be breaking this? It seems like the entire grid becomes unclickable as soon as I add the DataPager.

  10. 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

  11. 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

  12. 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

  13. 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.

  14. Ajith says:

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

  15. 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();
    }

  16. 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.

  17. 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.

  18. Jeremy says:

    Very nice! Thanks for posting this.

  19. 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?

  20. @Fallon,

    No problem – thanks for your feedback.

    Colin E.

  21. Fallon Massey says:

    Colin, forget that question.

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

  22. 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.

  23. 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