ExpandoObject in Silverlight – Take 2

In my previous blog we looked how easy it to use dynamic / ExpandoObject in Silverlight. But if you notice the sample code, I cheated big time there. Even though I created a dynamic class on the fly, the binding did happen to a known properties of the ViewModel. Have a look at the XAML I used in the previous example

Code Snippet
  1.  
  2. <TextBlock Text="First Name" FontSize="14"/>
  3. <TextBlock Text="{Binding FirstName}" FontSize="14" Grid.Column="1"/>
  4. <TextBlock Text="Last Name" FontSize="14" Grid.Row="1"/>
  5. <TextBlock Text="{Binding LastName}" FontSize="14" Grid.Row="1" Grid.Column="1"/>
  6. <TextBlock Text="Age" FontSize="14" Grid.Row="2"/>
  7. <TextBlock Text="{Binding Age}" FontSize="14" Grid.Row="2" Grid.Column="1"/>
  8. <TextBlock Text="Salary" FontSize="14" Grid.Row="3"/>
  9. <TextBlock Text="{Binding Salary}" FontSize="14" Grid.Row="3" Grid.Column="1"/>

You can see I am binding to a known fields FirstName, LastName, etc. If we were using dynamic classes these properties might not have been known during the design time so I couldn’t have bind the way I am binding here. So I cheated just to show how to create a dynamic objects but not the dynamic binding. Lets first try to bind to dynamic object itself and see what happens. To do that, I am going to change the XAML to bind to list of objects, whose properties are not known during design time (even though I am going to create them in code behind).

Code Snippet
  1. <c1:C1FlexGrid ItemsSource="{Binding People}" AutoGenerateColumns="True"/>

I am using ComponentOne FlexGrid to bind to list of Person to the grid. Now the ViewModel code which returns the People

Code Snippet
  1. List<object> _people = new List<object>();
  2.  
  3.         public List<object> People
  4.         {
  5.             get
  6.             {
  7.                 dynamic person = new ExpandoObject();
  8.                 person.FirstName = "Unni";
  9.                 person.LastName = "Nair";
  10.                 person.Age = 20;
  11.                 person.Salary = 10000;
  12.                 //_people.Add(new Person { FirstName = "Unni", LastName = "Nair", Age = 20, Salary = 10000 });
  13.                 _people.Add(person);
  14.                 return _people;
  15.             }
  16.         }

With this code, if you compile and run, you will notice, it will not display any rows. So there is something wrong here. When I search the net, I ran into a forum posting on the same subject. So as per Min-Hong,

“I am afraid, it is beacuse dynamic does not support reflection.

   Thus does not support databinding(in silverlight databinding uses reflection).”

I did couple of searches and hit a Silverlight feature list voting page on Microsoft, where lot of people requested this. It turned out, Silverlight 4 does not support this feature and I am not sure the state of Silverlight 5. But the good news is that, there were couple of work around to this problem. They are

  • Using emit to create a type using TypeBuilder. There are couple of posting on this model.One such example is here. I approached this model to create a sample code which would generate a 1000 rows and 50 columns and it took almost 2.6 seconds.
  • The second approach is using property bag model and using Silverlight’s suppport for indexer for binding. In this model, the same number of cell generation took only 1.4 seconds. So I decided to stick to this model. I am not going to explain, since Xavior’s blog explained it so good.

So lets look at the code which display our Person List in data grid.

We will add a new class called DynamicDataContext as per Xavior explained in his blog

Code Snippet
  1. public class DynamicDataContext : DynamicObject, INotifyPropertyChanged
  2. {
  3.     private readonly IDictionary<string, object> propertyBag = new Dictionary<string, object>();
  4.  
  5.     public event PropertyChangedEventHandler PropertyChanged;
  6.  
  7.     /// <summary>
  8.     /// The indexer is needed to be able to use indexer-syntax in XAML
  9.     /// to data bind to the properties available in the private property bag.
  10.     /// </summary>
  11.     /// <param name="index">The name of the property.</param>
  12.     /// <returns>The value of the property, or null if the property doesn't exist.</returns>
  13.     public object this[string index]
  14.     {
  15.         get
  16.         {
  17.             object result;
  18.             propertyBag.TryGetValue(index, out result);
  19.             return result;
  20.         }
  21.         set
  22.         {
  23.             propertyBag[index] = value;
  24.             RaisePropertyChanged(index);
  25.         }
  26.     }
  27.  
  28.     public override bool TryGetMember(GetMemberBinder binder, out object result)
  29.     {
  30.         return propertyBag.TryGetValue(binder.Name, out result);
  31.     }
  32.  
  33.     public override bool TrySetMember(SetMemberBinder binder, object value)
  34.     {
  35.         propertyBag[binder.Name] = value;
  36.         RaisePropertyChanged(binder.Name);
  37.         return true;
  38.     }
  39.  
  40.     private void RaisePropertyChanged(string propertyName)
  41.     {
  42.         if (PropertyChanged != null)
  43.             PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  44.     }
  45. }

Since the dynamic object does not support reflection, to show all the properties of the dynamic class in the grid, we need do couple of things in XAML and its code behind. First stop XAML

Code Snippet
  1. <c1:C1FlexGrid Name="_flex" ItemsSource="{Binding People}" AutoGenerateColumns="False"/>

We need to make sure we are not auto generating the columns since Silverlight does not know which are the columns we are going to have in grid, we need to manually create it in the code behind and then add it to the grid. So to do that we will mark the auto generate columns to ‘False’.

Before we go into code behind and see how we create the columns, first look at the ViewModel and see the what are we doing there.

First stop we create the list of DynamicDataContext for People property which is bound to the grid as

Code Snippet
  1. List<DynamicDataContext> _people;
  2.  
  3.         public List<DynamicDataContext> People
  4.         {
  5.             get
  6.             {
  7.                 if (_people == null)
  8.                     GenerateDynamicList();
  9.                 return _people;
  10.             }
  11.         }
  12.  
  13.         private void GenerateDynamicList()
  14.         {
  15.             _people = new List<DynamicDataContext>();
  16.             var person = new DynamicDataContext();
  17.             person["FirstName"] = "Unni";
  18.             person["LastName"] = "Nair";
  19.             person["Age"] = 20;
  20.             person["Salary"] = 10000;
  21.             _people.Add(person);
  22.             EventAggregator.Publish(new List<string> { "FirstName", "LastName", "Age", "Salary" });
  23.         }

Line 3-11 we create the People which is what bound to our grid in XAML.

When we generate the person collection, at the end, we publish an event for view to create the associated columns in the grid, by passing all available column names. It is done by publishing an event with list of column names at line 22. This event publishing is Jounce model of passing information between components.

Now lets look at the view code behind.

Code Snippet
  1. [ExportAsView("MainPage", IsShell = true)]
  2.     public partial class MainPage : UserControl, IEventSink<List<string>>, IPartImportsSatisfiedNotification
  3.     {
  4.         public MainPage()
  5.         {
  6.             InitializeComponent();
  7.         }
  8.  
  9.         [Export]
  10.         public ViewModelRoute Binding
  11.         {
  12.             get { return ViewModelRoute.Create("MainViewModel", "MainPage"); }
  13.         }
  14.  
  15.         [Import]
  16.         public IEventAggregator EventAggregator { get; set; }
  17.  
  18.         public void HandleEvent(List<string> publishedEvent)
  19.         {
  20.             foreach (string name in publishedEvent)
  21.             {
  22.                 Column col = new Column();
  23.                 col.Binding = new System.Windows.Data.Binding(string.Format("[{0}]", name));
  24.                 col.Header = name;
  25.                 _flex.Columns.Add(col);
  26.             }
  27.         }
  28.  
  29.         public void OnImportsSatisfied()
  30.         {
  31.             EventAggregator.Subscribe<List<string>>(this);
  32.         }
  33.     }

This is all Jounce here. When view model send the list of string, (don’t write production code like this 🙂 ) the view event sink intercepts and perform Handle Event method. For every element in the list, we create a new column and add it to our grid to display. That’s about it. Now if you would run this, you will get the result like the following

image

Very simple huh? I think Xaviour’s blog was exceptional in explaining the DynamicDataContent and how we use Silverlight 4’s indexer to identify and bind the dynamic columns.

I want to stop here with couple of points.

1. First of all, we need to create all the columns for the gird in the code behind. So once the collection is build you can send an event over to view and build it there. But most of the cases, you will first identify all the columns that are required to build the columns, so before you start building the data collection, you send the information over to View and build the grid columns before hand.

2. I am sure you have noticed the INotifyPropertyChanged interface implementation, that means we can track the data change events.

3. In ‘DynamicDataContext’, all we are tracking is bound object’s value change. We can extent this further to add your Entity Framework’s entity in it so that you can make changes to the underlying entity in the Data Context itself so that you can make use Entity Frame work’s change tracking for back end data update.

4. You can event extend the ‘DynamicDataContext’ to create hierarchical classes and generate tree like grid with ease. I will try to modify the ‘DynamicDataContext’ in the future blog to show this ability.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s