UPDATE: This blog post was written with a SL2 DataGrid, for SL3, please see my updated blog post.
In my previous blog post I described a method for solving the commonly faced problem of binding a Silverlight DataGrid to dynamic data, the form of which is not know at compile time. This blog post extends on the method previously described, adding change notification, allowing the DataGrid to synchronise the UI with changes to the bound data and to allow the user to edit the DataGrid’s contents.
To briefly recap my previous post, the dynamic data is copied to a collection of Rows:
public class Row { private Dictionary<string, object> _data = new Dictionary<string, object>(); public object this [string index] { get { return _data[index]; } set { _data[index] = value; } } }
Where the row ‘properties’ which are implemented via the string indexer bound to the grid via a ValueConverter. The XAML for the DataGrid looks like the following:
<data:DataGrid Name="_dataGrid" AutoGenerateColumns="False" IsReadOnly="False" Margin="5"> <data:DataGrid.Columns> <data:DataGridTextColumn Header="Forename" Binding="{Binding Converter={StaticResource RowIndexConverter}, ConverterParameter=Forename}"/> <data:DataGridTextColumn Header="Surname" Binding="{Binding Converter={StaticResource RowIndexConverter}, ConverterParameter=Surname}"/> ... </data:DataGrid.Columns> </data:DataGrid>
Where the ValueConverter takes the ConverterParameter and uses it to index the bound Row instance:
public class RowIndexConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { Row row = value as Row; string index = parameter as string; return row[index]; } }
It is important to note that the Binding does not have a Path defined. As a result, the binding source is the Row instance itself rather than a property of the Row as would more normally be the case when binding data. The rest of the post described how to create a collection view which sorts the collection via the Row’s string indexer. You can read about this in full in my previous blog post.
Now, one question a few people have asked me is how to implement INotifyPropertyChanged on the Row class in order to make the DataGrid editable. Simply implementing INotifyPropertyChanged on the Row class, with events raised from the index setter, will not make the grid editable or synchronised with the bound data. The underlying problem here is that the Binding does not have a Path specified, therefore, the binding framework does not know which property name to look out for when handling PropertyChanged events. Interestingly I would expect the above bindings to be updated if a PropertyChanged event were raised with a null or empty PropertyName string, however they do not.
So if the Binding framework needs a Path for the binding in order for change notification to work, we will have to fabricate one for this purpose! We can modify the Row class as follows, adding a Data property which is simply a reference to the Row instance itself:
public class Row : INotifyPropertyChanged { private Dictionary<string, object> _data = new Dictionary<string, object>(); public object this [string index] { get { return _data[index]; } set { _data[index] = value; OnPropertyChanged("Data"); } } public object Data { get { return this; } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string property) { if (PropertyChanged!=null) { PropertyChanged(this, new PropertyChangedEventArgs(property)); } } }
In order to bind this class we modify our bindings so that the source Path points to the Data property:
... <data:DataGridTextColumn Header="Forename" Binding="{Binding Path=Data, Converter={StaticResource RowIndexConverter}, ConverterParameter=Forename}"/> <data:DataGridTextColumn Header="Forename" Binding="{Binding Path=Data, Converter={StaticResource RowIndexConverter}, ConverterParameter=Surname}"/> ...
With this change in place, all of our bindings are bound to the Row’s Data property. Again, the Row’s indexer is being accessed via our value converter. However, if any ‘property’ change via the string indexer setter will result in all of our bindings being updated for that Row. I know, it is not the most efficient solution – but at least it work!
The next problem is making the DataGrid editable. The problem we face here is that all of our bindings are to the same property, Data. When a cell is edited, the binding framework will quite happily call the Data properties setter, with the updated value. However, how do we know which property of the Row was really being set? The only place we hold this information is in the ConverterParameter which is fed to our RowIndexConverter value converter. Fortunately, there is a solution to this problem, when the binding framework updates the bound object, it will first invoke the ConvertBack method on our value converter. This gives us the opportunity to ‘capture’ the converter parameter and feed it into the setter of our Data property, as shown below:
public class RowIndexConverter : IValueConverter { ... public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return new PropertyValueChange(parameter as string, value); } }
Where PropertyValueChanged is a simple value object:
public class PropertyValueChange { private string _propertyName; private object _value; public object Value { get { return _value; } } public string PropertyName { get { return _propertyName; } } public PropertyValueChange(string propertyName, object value) { _propertyName = propertyName; _value = value; } }
The setter for the Data property will now be invoked with an instance of PropertyValueChanged, giving it all the information it requires to invoke the string indexer with the correct ‘property’ name:
public class Row : INotifyPropertyChanged { ... public object Data { get { return this; } set { PropertyValueChange setter = value as PropertyValueChange; _data[setter.PropertyName] = setter.Value; } } ... }
With this change in place, we the user is able to edit the data bound to the DataGrid, and the binding ensures that the DataGrid UI is synchronised with any changes to the bound data that may happen elsewhere in our code.
One final thought that struck me is that this technique uses the Binding’s associated ValueConverter to access the Row’s string indexer, but what if we wanted to use a ValueConverter for something else like formatting? Fortunately it is a pretty straightforward process to nest one value converter inside another. For details, see the attached project. For now, have quick play with the grid below, where all the columns are editable and the button click event sets the Row’s Age property demonstrating how the DataGrid is synchronised:
private void Button_Click(object sender, RoutedEventArgs e) { // demonstrate the property changes from code-behind still work. Random rand = new Random(); foreach (var row in _rows) { row["Age"] = rand.Next(10) + 5; } }
Also, the Age column has a value converter associated …
So there you have it, binding to dynamic data with INotifyPropertyChanged implemented and an editable DataGrid. The only reservation I have with this technique is that I am really bending the binding framework to achieve the results, having a Data property which expects different types for the getter and setter is a little ugly … use with caution!
You can download the project sourcecode: silverlightdynamicbinding2.zip
Regards, Colin E.
Tags: binding, DataGrid, silverlight




wow … this actually work fine with checkboxcolumn too …. fantastic… you solve my headache
wonder if it actually works for checkboxcolumn
Thanks for this blog !!! it’s been a great help . Million Thanks.
[...] 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 [...]
>DataGrid Column not editable.
Sorry, I add DataGridTextColumn to IsReadOnly=”False” settings.
Ok, Column is editable.
I click column header. it’s error NotImplementedException.
It’s Great Post!
But it don’t work SL3.
DataGrid Column not editable.
This is a very nice approach, however it doesn’t really work in SL 4.0 Beta.
What happens is that Data Grid’s column becomes not editable when it has Path=Data in Binding. Without Path the column becomes editable, but ConvertBack doesn’t work for obvious reason. My first thought was maybe that’s because Data property is readonly, but it’s not in your source codes. I wonder if MS changed Binding in SL4 which causes this issue.
Thanks for your research anyway!
Hi, Thanks for the info. I have not tried this code with SL4 yet – if I do manage to fix the problem I will update this article.
Regards, Colin E.
nm.. saw you had an answer to this in part one
Great post
Question: Can you add the bindings in the code behind?
i.e. can this line be converted to c#…
Binding=”{Binding Converter={StaticResource RowIndexConverter},
ConverterParameter=Surname}”/>
Thank you for the comprehensive write-up of this technique. Works a treat!
Just want to say thank you for this post, it helped me out a lot!
Its Great Solution.
Appreciated. Thanks for sharing. it took 3-4 month for me to get these solution.
Hey Can help to build the Filter, Group and Sorting on the Collection.
I am working on the SL3.
OR you can write some hint I will work on it.
Thanks in advance
Regards,
Nikhil
@Nikhil,
Sorry – no plans for moving to SL3 yet.
Colin E.
Hi! With the new PagedCollectionView in SL3, would you implement custom sorting and filtering in a different way, or would you continue with this solution?
@Larsi,
Sorry – not played with v3.0 much – I’ll let you know if I do though.
Colin E.
Nice sample. I tried to hook up the Filter predicate but couldnt get it to be called. Have you played around with custom filtering at all with this technique?
Thanks
Matt
@Matt, Sorry – no, haven’t tried filtering. This codeproject article might be of some use to you:
http://www.codeproject.com/KB/silverlight/autofiltering_silverlight.aspx
Regards, Colin E.
Hi, great post!
One note on Silverlight 3: It seems you must explicitly add IsReadOnly=”False” to each column in the XAML, despite the default settings.
I added implementation of my class, that generates classes that implement INotifyPropertyChanged http://www.bodurov.com/files/DataSourceCreator_INotifyPropertyChanged.zip
Hi Vladimir,
Sounds great. Your solution is certainly very popular – I know of a few people who are currently using it. Having INotifyPropertyChanged is a welcome addition.
Regards, Colin E.
I have a situation, using WPF application, where I need to show some an attributes of a child object on the same row as the parent.
Here is the high level description. Can you recommend a way I can accomplish this perhaps using a variation on this technique.
A simple example would be like this )
Customer has a collection of orders
Each Order has a collection of Order Details.
Each OrderDetail has an attribute called “SumTotal”
In my grid, I need to display the following rows:
Row 1: Order.attribute1 |Order.attribute1 | Orderdetail[0].SumTotal | Orderdetail[1].SumTotal | Orderdetail[2].SumTotal |
Row 2: Order.attribute1 |Order.attribute1 | Orderdetail[0].SumTotal | Orderdetail[1].SumTotal | Orderdetail[2].SumTotal |
Row 3: Order.attribute1 |Order.attribute1 | Orderdetail[0].SumTotal | Orderdetail[1].SumTotal | Orderdetail[2].SumTotal |
Do you think this approach will work in this scenario?
Any recommendations?
Hi Tracy,
If you are using WPF – you are in luck! The binding framework is much more powerful, with the Binding.Path allowing you to bind to a path such as ‘OrderDetail[0].SumTotal’.
If you get stuck, I would try posting a question on the WPF Toolkit forums:
http://www.codeplex.com/wpf/Thread/List.aspx
You might want to add a little bit of code to illustrate your problem.
Regards, Colin E.
@hyper – Yeah, you have to get that part right, because it’s not very dynamic if the columns are fixed(lol).
When i try to generate the columns in page.xaml.cs file in SL3b, at rum time i got the
error for binding as below:
Error:
———-
Invalid Binding Path; character { in {Binding Path=Data, Converter={StaticResource RowIndexConverter}, ConverterParameter=Forename}.
My code:
———-
DataGridTextColumn dgtc = new DataGridTextColumn();
dgtc.Header = “Forename”;
dgtc.Binding = new Binding(“{Binding Path=Data,
Converter={StaticResource RowIndexConverter}, ConverterParameter=Forename}”);
dgtc.Width = new DataGridLength(100);
dgtc.CanUserSort = true;
dgtc.SortMemberPath = “Forename”;
How can i do the binding in .cs file. Thanks For All Help.
Hi,
The stuff in-between the curly braces {…} is a markup extension – this can only be used within XAML. To see the syntax for creating bindings in code-behind see the following article:
http://www.scottlogic.co.uk/blog/colin/2009/02/elementname-binding-in-silverlight-via-attached-behaviours/
Look at the CreateRelayBinding method.
Regards, Colin E.
@Colin – Yeah, a simple solution does exist, but it’s either hard to do, or Microsoft has some other reason I don’t understand as to why we can’t have it.
If we simply had the ability to create a type at runtime, it would solve a lot of these problems. That way you could send over weakly typed data and then populate the dynamically created types.
It’s probably just wishful thinking that we could get it, but there it is.
I asked, and you delivered.
Thanks Colin, I really do appreciate your blog.
Keep up the good work!
Hi Fallon.
No problem. It is an interesting problem – one which is fun to try and solve. I am still sure there must be a better way!
Regards, Colin E.
In C#/WPF you can use INotifyPropertyChanged to notify for Indexers by simply notifying “Item[]“. Hopefully it should work in Silverlight too.
public string this[int index]
{
get { return _myList[index]; }
set {
_myList[index] = value;
Notify(“Item[]“);
}
}
Thanks Mr. Squirrel, I didn’t know that one! I am sure it will come in handy one day for WPF … but not Silverlight!
Unfortunately the Silverlight binding framework cannot create bindings to indexers (with an index value), so unfortunately this PropertyChanged syntax is of little use in Silverlight binding.
If you have any other alternatives I would be interested to hear them. I have brainstormed a lot on this problem, but I am sure i have no exhausted all the possibilities.
Regards, Colin E.
Binding a Silverlight DataGrid to dynamic data Part 2 – editable data and INotifyPropertyChanged – Colin Eberhardt’s Adventures in WPF…
9efish.感谢你的文章 – Trackback from 9eFish…
Binding a Silverlight DataGrid to dynamic data Part 2 – editable data and INotifyPropertyChanged – Colin Eberhardt…
Thank you for submitting this cool story – Trackback from DotNetShoutout…
Great! I confused how I can bind by two ways before. Thanks for your tips.
[...] / Next Silverlight Dependency Property Code GenerationBinding a Silverlight DataGrid to dynamic data Part 2 – editable data and INotifyPropertyChanged [...]