Friday, November 14, 2008

A Dynamic SilverLight DataGrid.


Note: this article is about how to set the values for the DataGrid. If you want to see how to add columns dynamically check out this article: http://blogs.msdn.com/scmorris/archive/2008/04/14/defining-silverlight-datagrid-columns-at-runtime.aspx

I’ve recently come across 3 very annoying limitations in Silverlight 2.0 while trying to use the data grid. The first is that the DataTable class does not exist. This is the typical way to set the ItemSource for other .Net DataGrid/GridViews. The second is that Databinding in Silverlight CANNOT do indexes or any other special kind of binding (http://msdn.microsoft.com/en-us/library/cc189022(VS.95).aspx) it only supports property bindings. So the idea of just binding to array[index] cannot be done. The third is there is no manual way to add rows to the DataGrid. You can only set ItemsSource. Its like they ca

So the only way to feed the Datagrid is to have a class collection with the properties you need. So obviously Microsoft does not want you to have dynamic datagrids. One work around is to use IConvertor (http://blogs.msdn.com/deepak_verma/archive/2008/07/19/dynamic-creation-binding-of-silverlight-datagrid.aspx) But this only allows one way databinding and for my project it requires two way databinding. So I obviously can’t use that.

Other people have concluded the only way around this is to use code generation to create the classes with the properties they need. As cool and powerful as code generation is, I find it to be buggy, hard to debug, and generally very nasty to work with. Since I didn’t have the time or energy to write my own CodeGen solution and can’t use any 3rd party code I figured out another solution that is easier to do, works, but is potentially very slow for Grids with a large number of columns.

Basically you use a linked list for your row. This can be done because every element in a linked list is accessible via a property. The “Next” property in a link list is what makes this possible and what can make it slow. To access the nth column you just have n-1 “Next”s in the Path followed by “Value”. In the code below I am setting a column’s Binding property.

column.Binding = new Binding(CreatePath(columnIndex));

string
CreatePath(int columnIndex)
{
StringBuilder result = new StringBuilder();
for
(int index = 0; index < columnIndex; index++)
{
result.Append("Next.");
}
result.Append("Value");
return
result.ToString();
}


When creating the actual ItemsSource create a list of link list nodes and fill it with the first node from each row. In the below code the trueSource is just a sample collection that would work.


var
trueSource = new object[][]
{
new []{ "Row", "One" },
new
[]{ "Row", "Two" },
};

List
<LinkedListNode<object>> itemSource = new List<LinkedListNode<object>>();
foreach
(var item in trueSource)
{
var row = new LinkedList<object>(item);
itemSource.Add(row.First);
}
dataGrid.ItemSource = itemSource;


There you have it. It’s very hacky, it’s potentially slow and code generation is your only other option (that I know of). Microsoft really dropped the ball on this one.

If you have yet to figure out why it can be slow here is the reason: let’s say you have 32 columns in order to bind to the last element the “Next” property has to be called 31 times to get to the node that contains its “Value”. That is 31 extra calls then a direct binding. Since data binding uses reflection that will make it even slower. But with machines being so fast as they are now I doubt you will notice the speed hit, unless you have a lot of columns and a lot of rows. If that’s the case Code generation and all its hair pulling fun is your only other option (that I know of).

2 comments:

Xiard said...

Thanks for the post. Hacky it may be, but it sounds like a pretty clever workaround to me.

You may not have needed to do this, but did you come up with any way to get notified when a change is made to the source data? I have used your mechanism to setup binding from a TextBox to array data, but I need to know when the LinkedList is updated (or particular nodes are updated).

I can write my own "ObservableLinkedList", but I would rather use/extend the stock LinkedList if possible.

Thanks again for your post,

David Cater

Xiard said...

FYI: I ended up creating an "ObservableLinkedList" based on LinkedList (from Reflector). Here's my blog post on it, including a link to some uploaded code:

http://xiard.spaces.live.com/blog/cns!1F832F42CA0A4841!212.entry

David