Dynamic binding for your Silverlight applications

by Vladimir Enchev | Comments 32

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!

,
Senior Technical Architect

32 Comments

Jaime Bula
Awesome post!! We'll be updating our project right away!
All Thumbs Up!
Andrew Skalkin
Will it work fast with hundreds of thousands of rows and hundreds of columns?
Vlad
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
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
Hi Frenk,
If you want to bind the grid columns from a view model you can check this project for more info. 
Frenk
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
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
Indeed dynamics are not supported in Silverlight 4 bindings - you can use the indexer. 
Jorge Cossio
Excellent Vlad... Just in time! I was implementing the old version and now I will try with this new one.
Andrew Skalkin
@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
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
@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
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
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
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
Do yuo have some example with MVVM?
Sri
I am not able to view the column in my gridview. I am using ObservableCollection<dynamic>. Can you share the sample application?
christine
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
found the solution :)
Arryf
Hi Vlad,
can we use telerik zip library class in this scenario to reduce data traffic? How?
Thanks
Jackie G
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
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
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
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
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
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
Gio
This is exactly what we need but unfortunately it doesn't seem to work with Version 2011.1.315.1040. What's the lowest version required?
Rafal
Hi Vlad,
Thank you for this method, however I have a one problem. When I added rows in following way then values aren't showing.
row["123456"] = 100; // not working
row["123456Text"] = 100; // not working
row["Text123456"] = 100; // working
string columnName1 = "Example column name";
row[columnName1] = 100; // working
string columnName2 = "Example \"column\" name";
row[columnName2] = 100; // not working
Do you have any clues?
Andy
Will this work with ExpandoObjects?
Paul
Hi Vlad, 
This looks great, exactly what we need. I'm able to populate an ObservableCollection<dynamic> with DataRows. This collection seems to bind to the grid (the collection's getter is called) but no columns are autogenerated! The GetDynamicMemberNames method is never called! Subsequently, no data is shown What could be the issue here?

Jackie
Vlad or Jay,

Were you able to get the filtering to work propery for recognizing the data type of the columns? 

    
Adam
Hello, 
Awesome tool. But I am having an issue.
Loads everything ok. the problem I am having is when I tried to get the data out of the grid.

I am trying to write something like this...

<code>
                DataRow row = (DataRow)rgvPhysical.SelectedItems[0];
                string serial = rgvPhysical.Columns["kinventory_id"].GetValue;

</code>

But I am getting an error at run time: 
'Unable to cast object of type 'DynamicClass1' to type 'Telerik.Data.DataRow'.' 


Its like the control is not getting loaded with DataRows.... but I can see it that it is getting loaded.

<code>
            var client = new svGridDataClient();
            client.GetDataCompleted += (s, e) =>
            {
                rgvPhysical.ItemsSource = new DataTable(e.Result);
            };

            client.GetDataAsync(sql);

</code>

Please help ! Brain is melting....




Comments

  1.    
      
      
       
  2. (optional, emails won't be shown on public pages)
  3. (optional)
Read more articles by Vladimir Enchev - or - read latest articles in Developer Tools
Product Families