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

April 22nd, 2009 by Colin Eberhardt

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

43 Responses to “Binding a Silverlight DataGrid to dynamic data Part 2 – editable data and INotifyPropertyChanged”

    • Andre says:

      Hello Colin,

      I read your blog posting „Binding a Silverlight DataGrid to dynamic data Part 2 – editable data and INotifyPropertyChanged”.
      Is it possible to bind some textboxes too, with a two-way binding to the datasource. I tried it below but it did not work.
      public Page()
      {
      InitializeComponent();

      // generate some dummy data
      Random rand = new Random();
      for (int i = 0; i < 200; i++)
      {
      Row row = new Row();
      row["Forename"] = s_names[rand.Next(s_names.Length)];
      row["Surname"] = s_surnames[rand.Next(s_surnames.Length)];
      row["Age"] = rand.Next(40) + 10;
      row["Shoesize"] = rand.Next(10) + 5;
      _rows.Add(row);
      }

      Binding binding = new Binding("Shoesize");
      binding.Mode = BindingMode.TwoWay;
      binding.Converter = new RowIndexConverter();
      binding.ConverterParameter = "Shoesize";
      // binding.Source = _rows;
      textBox1.SetBinding(TextBox.TextProperty, binding);

      // bind to our DataGrid
      _dataGrid.ItemsSource = _rows;
      }
      }

      Maybe you can give me a hint.

      Best regards
      André

  1. Marcell says:

    Hi!

    I just want to thank you this great post. You helped me a lot. This is the best datagrid description that I’ve ever seen. Thanks again.

    Marcell

  2. BalamBalam says:

    Not sure about SilverLight but with WPF can pass an index in the Path
    e.g. Binding=”{Binding Path=Data[0]
    And you can even append a property name
    e.g. Binding=”{Binding Path=Data[0].GridDisplayValue
    Dynamic columns without a converter

    I have not tested editing
    Sample code below:

    ObservableCollection GridRowsWithDynamicColumns = new ObservableCollection();
    for (int i = 1; i < 10; i++)
    {
    GridRowsWithDynamicColumns.Add(new GridRowWithDynamicColumns(i));
    }

    DataGridTextColumn textColumn;
    Binding myBinding;

    // can mix a static column with dynamic
    textColumn = new DataGridTextColumn();
    myBinding = new Binding("RowID");
    textColumn.Header = "RowID";
    textColumn.Binding = myBinding;
    textColumn.IsReadOnly = true;
    DataGrid4.Columns.Add(textColumn);
    DataGrid4.FrozenColumnCount = 1;

    GridRowWithDynamicColumns gridRowWithDynamicColumns = new GridRowWithDynamicColumns(0);
    //gridRowWithDynamicColumns is just to get .GridDynamicColumns
    for (int i = 0; i < gridRowWithDynamicColumns.GridDynamicColumns.Count; i++)
    {
    textColumn = new DataGridTextColumn();
    // the constructor is Path
    myBinding = new Binding("GridDynamicColumns[" + i.ToString() + "].DisplayValue");
    // myBinding.Converter = new RowIndexConverter2(); converter not required
    textColumn.Header = gridRowWithDynamicColumns.GridDynamicColumns[i].ColumnHeader;
    textColumn.Binding = myBinding;
    textColumn.IsReadOnly = true;
    DataGrid4.Columns.Add(textColumn);
    }

    DataGrid4.ItemsSource = GridRowsWithDynamicColumns;

    public class GridRowWithDynamicColumns
    {
    private Int32 rowID;
    private List gridDynamicColumns = new List();

    public string RowID
    {
    get { return rowID.ToString(); }
    }

    public List GridDynamicColumns
    {
    get { return gridDynamicColumns; }
    }

    public GridRowWithDynamicColumns(Int32 RowID)
    {
    rowID = RowID;
    gridDynamicColumns.Add(new GridDynamicColumn(rowID, “ColumnA”));
    gridDynamicColumns.Add(new GridDynamicColumn(rowID, “ColumnB”));
    gridDynamicColumns.Add(new GridDynamicColumn(rowID, “ColumnC”));
    gridDynamicColumns.Add(new GridDynamicColumn(rowID, “ColumnD”));
    // Debug.WriteLine(gridDynamicColumns[0].DisplayValue);
    }
    }

    public class GridDynamicColumn
    {
    private string columnHeader;
    private Int32 rowID;

    public GridDynamicColumn(Int32 RowID, string ColumnHeader)
    {
    rowID = RowID;
    columnHeader = ColumnHeader;
    }

    public string DisplayValue
    {
    get
    {
    return rowID.ToString() + ” ” + columnHeader;
    // this could be much more complex such as a call to database
    // any parameters passed in the constructor are available
    // or could implement a mutator and pass DisplayValue in the constructor
    }
    }

    public Int32 RowID
    {
    get
    {
    return rowID;
    }
    }

    public string ColumnHeader
    {
    get
    {
    return columnHeader;
    }
    }
    }

  3. BalamBalam says:

    I ported this over to WPF and where it breaks is the call to Convert after a cell edit. At that point a row is not passed. This seemed to fix it:

    public object Convert(object value, Type targetType, object parameter, …)
    {
    Debug.WriteLine(value.GetType());
    string index = parameter as string;
    if (value.GetType() == typeof(Row))
    {
    Debug.WriteLine(“Row”);
    Row row = value as Row;
    return row[index];
    }
    else if (value.GetType() == typeof(PropertyValueChange))
    {
    Debug.WriteLine(“PropertyValueChange”);
    PropertyValueChange pvc = value as PropertyValueChange;
    return pvc.Value;
    }
    else
    {
    throw new NotImplementedException();
    }
    }

    I have not implemented or tested sorting in WPF .NET 4.0

  4. pb says:

    firing property changed on Item[PropertyName] works in SL4

  5. small says:

    wow … this actually work fine with checkboxcolumn too …. fantastic… you solve my headache

  6. small says:

    wonder if it actually works for checkboxcolumn

  7. Laural says:

    Thanks for this blog !!! it’s been a great help . Million Thanks.

  8. [...] 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 [...]

  9. sinh says:

    >DataGrid Column not editable.
    Sorry, I add DataGridTextColumn to IsReadOnly=”False” settings.
    Ok, Column is editable.

    I click column header. it’s error NotImplementedException.

  10. sinh says:

    It’s Great Post!
    But it don’t work SL3.
    DataGrid Column not editable.

  11. Dmitriy says:

    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!

  12. Grayson Mitchell says:

    nm.. saw you had an answer to this in part one

  13. Grayson Mitchell says:

    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}”/>

  14. David Saunders says:

    Thank you for the comprehensive write-up of this technique. Works a treat!

  15. Rachel says:

    Just want to say thank you for this post, it helped me out a lot!

  16. Nikhil says:

    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

  17. Larsi says:

    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?

  18. Matt H says:

    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

  19. Peet Brits says:

    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.

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

  21. Tracy B says:

    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.

  22. Fallon Massey says:

    @hyper – Yeah, you have to get that part right, because it’s not very dynamic if the columns are fixed(lol).

  23. hyper says:

    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.

  24. Fallon Massey says:

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

  25. Fallon Massey says:

    I asked, and you delivered.

    Thanks Colin, I really do appreciate your blog.

    Keep up the good work!

  26. WPF Squirrel says:

    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[]“);
    }
    }

  27. 9eFish says:

    Binding a Silverlight DataGrid to dynamic data Part 2 – editable data and INotifyPropertyChanged – Colin Eberhardt’s Adventures in WPF…

    9efish.感谢你的文章 – Trackback from 9eFish…

  28. 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…

  29. Kevin says:

    Great! I confused how I can bind by two ways before. Thanks for your tips.

  30. [...] / Next Silverlight Dependency Property Code GenerationBinding a Silverlight DataGrid to dynamic data Part 2 – editable data and INotifyPropertyChanged [...]

Leave a Reply