Dynamic binding for your Silverlight applications

Tuesday, October 04, 2011 by Vladimir Enchev | Comments 26

In the early days of Silverlight binding support for advanced dynamic scenarios was very limited (remember my DataTable?). Since then, however, the platform has evolved greatly – so did our tools!

 

Dynamic code 2009 style:
image

 

Dynamic code 2011 style:
image

 

Now to bind RadGridView to some dynamic data you just need ObservableCollection<dynamic>:
image

 

RadGridView will auto-generate columns automatically using your DynamicObject GetDynamicMemberNames() implementation and will call your TryGetMember/TrySetMember (TryGetIndex/TrySetIndex in case of indexer binding) methods when needed.

 

To illustrate this I’ve made small example project (similar to this blog post) demonstrating RadGridView bound to collection of custom dynamic objects populated with unknown data from a serialized DataTable using plain WCF service:

 

image

 

image

 

image

 

Enjoy!

26 Comments

  • Jaime Bula 04 Oct 2011
    Awesome post!! We'll be updating our project right away!
    All Thumbs Up!
  • Andrew Skalkin 05 Oct 2011
    Will it work fast with hundreds of thousands of rows and hundreds of columns?
  • Vlad 06 Oct 2011
    Thanks Jaime! 
    @Andrew The grid itself is capable to work in extreme scenarios like this one  (1 mil. rows with 1000 columns). I'm not sure however if 1 mil. data object with 1000 properties each is good idea memory-wise. 
  • Frenk 06 Oct 2011
    Vlad, this is seriously awesome.
    I'm experiencing a problem that was already there with the old lightweight dataTable when I don't let the grid autogenerate columns.
    I wrote an attached property (grid.Columns is not a dependencyProperty) to assign columns, and it looks like columns are created right in the middle of row rendering -which obviously causes a problem.
    Could I send you a simple change to your sample project to show you the issue?
  • Vlad 06 Oct 2011
    Hi Frenk,
    If you want to bind the grid columns from a view model you can check this project for more info. 
  • Frenk 06 Oct 2011
    Vlad,
    my scenario is much like the example you described in the post, where I receive a dataTable and must create a dynamic object out of it.
    It works fine when you use auto-generated columns, or only use the default cell template (that binds to what you specify in DataMemberBinding).
    The problem arises when you define a custom cell template. For example, assuming that columnName is the name of a dynamic property in MyDataRow:
    grid.Columns.Add(new GridViewDataColumn() 
                    { 
                        Header = columnName, 
                        DataMemberBinding = new Binding(columnName), 
                        CellTemplate = 
                            (DataTemplate)XamlReader.Load( 
                            string.Format( 
                            @"<DataTemplate xmlns=""http://schemas.microsoft.com/client/2007"" xmlns:grid=""http://schemas.telerik.com/2008/xaml/presentation""> 
                              <TextBlock Text=""{Binding {0}.UpperCaseValue}""/> 
                              </DataTemplate>", 
                              columnName)) 
                    })); 

    This does not work because apparently the binding engine cannot "discover" the dynamic property. You get a binding error "Property xxx not found on MyDataRow" where xxx is the value of columnName.
    You see this as well if you just create for ex. a button and bind its content to a dynamic property.
    Do you have any idea on how to overcome this?
  • Frenk 06 Oct 2011
    Nevermind, problem solved. You must define the binding as "{Binding [columnName]}", so that columnName is accessed through the indexer. Didn't know that this was possible.
  • Vlad 07 Oct 2011
    Indeed dynamics are not supported in Silverlight 4 bindings - you can use the indexer. 
  • Jorge Cossio 07 Oct 2011
    Excellent Vlad... Just in time! I was implementing the old version and now I will try with this new one.
  • Andrew Skalkin 19 Oct 2011
    @Vlad But woundn't this approach be very inefficient since the grid has to find out the properties of each row (of which we have millions) for creating grid layout?
  • Vlad 20 Oct 2011
    Hi Andrew,
    The grid will call GetDynamicMemberNames() just once (for the first item) to create the columns and will expect all other items to have exactly the same properties. All internal lambda expressions are compiled and cached - you will not get performance problems. 
  • Andrew Skalkin 20 Oct 2011
    @Vlad But woundn't this approach be very inefficient since the grid has to find out the properties of each row (of which we have millions) for creating grid layout?
  • Wendell 22 Oct 2011
    Hi Vlad, I'm using the dynamic columns approach of which you supplied the test project. Its works perfectly when the viewmodel is set as a resource but now I have a situation where the viewmodel is set in the datacontext, now you guessed it, the columns do not appear. I assumed that it would work itsself out but it doesnt. I've tried something like this:
                <i:Interaction.Behaviors>                <local:ColumnsBehavior Columns="{Binding Columns, Source=DataContext}" />            </i:Interaction.Behaviors>
    but to no avail please some help would be much appreciated.
  • John 17 Nov 2011
    Hi Vlad,
    First of all, thank you for your solution. I Would like to know wheter is an elegant way to add a display name for a column different from the original property name
  • John 17 Nov 2011
    Hi Vlad,
    First of all, thank you for your solution. I Would like to know wheter is an elegant way to add a display name for a column different from the original property name
  • Francisco Rodríguez 25 Nov 2011
    Do yuo have some example with MVVM?
  • Sri 28 Dec 2011
    I am not able to view the column in my gridview. I am using ObservableCollection<dynamic>. Can you share the sample application?
  • christine 10 Jan 2012
    Hi Vlad, i m trying to use your solution to bind the datagrid but all my cells show "System.Collections.Generic.GenericEqualityComparer 1[System.String] "  this value. Perhaps i must have missed something. Kindly let me know, what is to be done.
    Thanks
  • christine 19 Jan 2012
    found the solution :)
  • Arryf 18 Feb 2012
    Hi Vlad,
    can we use telerik zip library class in this scenario to reduce data traffic? How?
    Thanks
  • Jackie G 24 Feb 2012
    We have copied exactly what is here but we switched the connection string to use a local database and Select * from Table1 which is 22 columns and 10,000 rows. The code fails in the References.cs ("Public System.Collections.ObjectModel.ObservableCollection<System.Collections.Generic.Dictionary<string, object>> EndGetData(System.IAsyncResult result) { object[] _args = new object[0];  System.Collections.ObjectModel. ObservableCollection<System.Collections.Generic.Dictionary<string, object>> _result = ((System.Collections.ObjectModel.ObservableCollection<System.Collections.Generic.Dictionary<string, object>>)(base.EndInvoke("GetData", _args, result))); return _result;"

    The Error that is returned is "The remote server returned an error: NotFound."
    If I limit this query to 500 it runs (Top 500) it runs fine... Will someone else try this code against the AdventureWorks.Sales.SalesOrderDetail("select * from Sales.SalesOrderDetail"). This also gives the same error.
  • Jackie G 27 Feb 2012
    I found the issue. When you create a service it doesn't automatically add these following lines of code to the web.config. If you would have more than 65536 records you will run into the error.     <behavior name="">
         <serviceMetadata httpGetEnabled="true" />
         <serviceDebug includeExceptionDetailInFaults="false" />
          <dataContractSerializer maxItemsInObjectGraph="2147483647" />
        </behavior>
  • Jackie G 27 Feb 2012
    I found the issue. When you create a service it doesn't automatically add these following lines of code to the web.config. If you would have more than 65536 records you will run into the error.     <behavior name="">
         <serviceMetadata httpGetEnabled="true" />
         <serviceDebug includeExceptionDetailInFaults="false" />
          <dataContractSerializer maxItemsInObjectGraph="2147483647" />
        </behavior>
  • Andre C 18 Apr 2012
    Hi Vlad,
    How could I adapt this solution to allow the insertion of column definition? I want to define that, for example, the "ID" column is not groupable. Since the collection of MyDataRow automatically generates columns, the GridViewDataColumn definitions that I am inserting in the grid are now not working...
  • Jay Paolucci 08 May 2012
    Vlad,
    Thank you for this useful class, however I do have a question concerning how it handles filtering. It appears that my column filter options are those associated with a column of type object instead of the actual data type represented by the column. I was wondering if you might have some insight into why this might be happening.
    Thanks,
    Jay Paolucci
  • Jay Paolucci 08 May 2012
    Vlad,
    Thank you for this useful class, however I do have a question concerning how it handles filtering. It appears that my column filter options are those associated with a column of type object instead of the actual data type represented by the column. I was wondering if you might have some insight into why this might be happening.
    Thanks,
    Jay Paolucci

Add comment

  1. Formatting options
       
     
     
     
     
       
  2. (optional, emails won't be shown on public pages)
  3. (optional)