Click here to Skip to main content
15,889,867 members
Articles / Silverlight

Binding a Silverlight DataGrid to dynamic data Part 2 - editable data and INotifyPropertyChanged

Rate me:
Please Sign up or sign in to vote.
4.88/5 (4 votes)
17 May 2009CPOL4 min read 32.9K   14   3
This technical blog post shows how to bind a DataGrid to dynamic data where the properties are not know at design / compile-time.

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

C#
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’ are implemented via the string indexer bound to the grid via a ValueConverter. The XAML for the DataGrid looks like the following:

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

C#
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 was 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:

C#
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:

XML
...
<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 works!

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:

C#
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:

C#
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:

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

C#
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 source code here.

Regards, Colin E.

This article was originally posted at http://www.scottlogic.co.uk/blog/wpf?p=275

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Scott Logic
United Kingdom United Kingdom
I am CTO at ShinobiControls, a team of iOS developers who are carefully crafting iOS charts, grids and controls for making your applications awesome.

I am a Technical Architect for Visiblox which have developed the world's fastest WPF / Silverlight and WP7 charts.

I am also a Technical Evangelist at Scott Logic, a provider of bespoke financial software and consultancy for the retail and investment banking, stockbroking, asset management and hedge fund communities.

Visit my blog - Colin Eberhardt's Adventures in .NET.

Follow me on Twitter - @ColinEberhardt

-

Comments and Discussions

 
QuestionSwitching content at run time Pin
Al Shameer B18-Aug-11 22:30
Al Shameer B18-Aug-11 22:30 
NewsNow works with Silverlight 3 Pin
Colin Eberhardt26-Mar-10 20:05
Colin Eberhardt26-Mar-10 20:05 
GeneralChange cell colour on updation Pin
Avani Vadera23-Nov-09 21:59
Avani Vadera23-Nov-09 21:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.