Friday, July 30, 2010

Silverlight and ViewModels and ECO and Model Driven Development

…and a few other things like xaml design time binding support.

To get any of data that the model represent into a silverlight UI you must have a Silverlight project and a silverlight model and a silverlight ecospace – we have not yet extended the Wizard to handle Silverlight projects so if you want to get started right away I suggest you do so by extending the sample named SLDemo20100730 in the Demos folder of the latest ECO build.

There is not anything special about a silverlight model or ecospace – but they do reference other  assemblies – like Eco.Handles.SL instead of Eco.Handles.

Suppose you have this model:

Model from Modlr


Then we can go like this:

   1: public Page()
   2: {
   3:     InitializeComponent();
   5:     // Start the UI dequeuer that decouples the UI-updates from the Domain object changes
   6:     // This is an important design pattern when having fast changing domain object properties
   7:     // We need just do this once per application
   8:     // This call is unique to Silverlight, in WPF it is called DequeuerWPF
   9:     DequeuerSL.Active = true;
  11:     // Create the ecospace and activate it
  12:     // Many applications has a single ecospace, but it is lightweight and 
  13:     // you can create multiple if you want to
  14:     _ecoSpace = new EcoSilverlightDemoEcoSpace();
  15:     _ecoSpace.Active = true;
  18:     // Create some demo data 
  19:     // Since the ECO-Framework has provided code for our model, this is just a matter of assigning
  20:     // properties and associations
  21:     _person = new Person(_ecoSpace) { FirstName = "John", LastName = "Smith", DateOfBirth = DateTime.Now };
  22:     _person.Home = new Building(_ecoSpace) { Address = "EasyStreet 1" };
  24:     Building b2 = new Building(_ecoSpace) { Address = "EasyStreet 2" };
  25:     Building b3 = new Building(_ecoSpace) { Address = "EasyStreet 3" };
  26:     Building b4 = new Building(_ecoSpace) { Address = "EasyStreet 4" };
  28:     _person.OwnedBuildings.Add(b2);
  29:     _person.OwnedBuildings.Add(b3);
  30:     _person.OwnedBuildings.Add(b4);


To get this data displayed we can of course be very naive:

   1: // The most naive and straight forward way to get data into the view is to simply
   2: // assign values to the Text properties of our TextBoxes
   3: // This will not catch changes to the textboxes and it will not get updated if the
   4: // _person properties gets updated elsewhere.
   5: tbFirstName.Text = _person.FirstName;
   6: tbLastName.Text = _person.LastName;
   7: FullName.Text = "FullName: " + _person.FullName;

But let us move on with databind:

   1: // The second most naive way - but still a lot better - is to use databind straight 
   2: // to the domain objects. This is a common strategy in all Silverlight applications - 
   3: // model driven or not, and solutions may very well only use this technique to build 
   4: // a complete application.
   5: // To get data for you to bind to into a datacontext you assign a domain object 
   6: // to a it - the datacontext is inherited by contained controls so all controls in our 
   7: // StackPanel get access to the domain object of type Person
   8: StackPanelWithDataContextAndBinding.DataContext = _person;

With the following XAML:

   1: <StackPanel x:Name="StackPanelWithDataContextAndBinding" Background="LightBlue">
   2:     <TextBlock Text="Adding the Person object to StackPanel.DataContext and setting up twoway binding"></TextBlock>
   3:     <TextBox Width="200" Height="20" Text="{Binding Path=FirstName,Mode=TwoWay}">
   4:     </TextBox>
   5:     <TextBox Width="200" Height="20" Text="{Binding Path=LastName,Mode=TwoWay}">
   6:     </TextBox>
   7:     <TextBlock Width="200" Height="20" Text="{Binding Path=FullName,Mode=OneWay}">
   8:     </TextBlock>
   9:     <TextBlock Text="Doing it twice just to prove databind"></TextBlock>
  10:     <TextBox Width="200" Height="20" Text="{Binding Path=FirstName,Mode=TwoWay}">
  11:     </TextBox>
  12:     <TextBox Width="200" Height="20" Text="{Binding Path=LastName,Mode=TwoWay}">
  13:     </TextBox>
  14:     <TextBox Width="200" Height="20" Text="{Binding Path=Home.Address,Mode=TwoWay}">
  15:     </TextBox>    
  16: </StackPanel>

Gives us this output:


The “Doing it twice just to prove databind” reference in the code and image above is just a convenient way to  ensure that the value is applied and propagated to subscribers of that value – since if I change the value in one place it should update in the other control and vice versa; if everything works that is.

And to bind to a collection of some sort:

   1: // To bind to collections that are subscribed - meaning that they are maintained by some
   2: // underlying rule - you need to wrap them in a EcoObservableCollection like we do below.
   3: // The underlying rule that manages the content might be a domain object association or
   4: // the result from the Extent service like below that we use to get a subscribed collection
   5: // of all Buildings in this case.
   6: EcoObservableCollection<Building> x = new EcoObservableCollection<Building>(
   7:                 EcoServiceHelper.GetExtentService(_ecoSpace).AllInstances<Building>());
   8: GridWithBuildings.ItemsSource = x;

Some xaml like this is needed for the display of the collection:

   1: <sdk:DataGrid x:Name="GridWithBuildings" Height="100" AutoGenerateColumns="False" >
   2:     <sdk:DataGrid.Columns>
   3:         <sdk:DataGridTextColumn IsReadOnly="False" 
   4:                                 Header="Address" 
   5:                                 Binding="{Binding Address,Mode=TwoWay}">
   6:         </sdk:DataGridTextColumn>
   7:     </sdk:DataGrid.Columns>    
   8: </sdk:DataGrid>

and we get this output:


As soon as the UI requires the data to be mangled/massaged/transformed or what you may call it, you quickly run into trouble with the strategy to bind straight to the domain objects. The “trouble” can be dealt with by use of derived properties and derived associations in the Model - and this is a good solution but not perfect.

The reason why I think it is not perfect is that it moves concerns of the presentation (the need for transformation) into the model – and indeed this might be a non issue and even desired if multiple UI’s has the very same need. But there will be times when you find yourself trying to transform the presentation of data in a way that does not quite seem generic enough for making it into the main model.

You can also easily find cases when the UI wants to filter some association based on a transient setting provided in the display – like “Only show orders not sent by this date” – in this case the user has been given an opportunity to provide a date and you most certainly do not want to have that filter date in the model – but you need it to define a derived association to get to the data.

Further needs will become clear when you deal with tuples of data – read more about tuples in this post.

Xaml Bindings has a Converter mechanism built in that can be useful from time to time but I does require code and references to that code so it is not my favorite in dealing with these issues. It is however essential when doing things like type conversions between a byte array and an image etc.

Enough with dwelling on the problems – let us look at the solution. The perfect solution, in my opinion, is to allow for a transform-area/rendering-area between the UI and domain model so that you can clearly decouple the two.

This is what we call a ViewModel - a ViewModel is a transformation of a part of the domain model where new attributes and associations are formed as transformations from original ones in the domain model with the sole intention to be data easily displayed by a UI.

Defining a ViewModel is very easy and I do recommend everyone to use them. ECO allows for both standard ViewModels that only focus one data transformation/mangling, but also the ViewModels with UI-hints that will help you to quickly build up standard user interfaces. For Silverlight I have not started on the latter kind with UI hints, but I will get on it right after this.

Challenges with Silverlight

Silverlight does not have support for some of the core things that we previously have built ECO-Handles on. ECO-Handles have been around since very early days and are a predecessor of ViewModels but also used internally by the different ViewModel implementations. What they do is to provide a mangling/transformation area just like ViewModels do.

The main difference between Handles and ViewModels is that Handles are normally placed on a form very close to the UI control that has need for data. And that it usually took many handles (expressionhandles typically) to get one form ready for release. The down side of this approach was that the application got a lot of tiny transformations (one per Handle-column) scattered around the application forms. Something one needed to be aware of when changing the model and thus potentially breaking existing scattered transformations. This was all according to the patterns that Windows forms and ASP.NET was built around – and I guess it emerged from the constant use of datasets some ten years ago.

The ViewModel address these challenges by holding on to a collection of transformations of all or at least several of the transformations needed for a whole UI (form/UserControl), and keeping this at model/application level rather than at the forms level. This enables us to provide a static check of all these transformations to verify that they still are valid (right click the model surface, Extras, Show model errors). Since the ViewModel also holds a straight forward way to define associations between ViewModelClasses one can get a better overview of what one wants to achieve with the ViewModel. To be fair I must state that Handles also had the ability to define derived associations – Nestings. But the ViewModels make the Nesting concept less cryptic.

Years ago I wrote a post on ECO where I added this “Note to self: write a property editor so that you can actually understand this” – With “this” I meant the Nestings in the Handles – so now I have come full circle on that subject. Good for me.

But as I said in the beginning the Handles got impaired by meeting Mr. Silverlight – Since Silverlight does not provide the ICustomTypeDescriptor that was core to the RenderedTuple objects that made up the “Objects” or “Rows” of the Handles – we needed to do something different.

As .net4 and Silverlight4 both provide the DynamicObject I thought that it would be a no-brainer to replace the backend of the RenderedTuple descriptor. But as it turned out the DynamicObject does not work when it comes to Binding in Silverlight– the one thing we needed it for.

What does however work in both Silverlight and WPF is Binding to Dictionaries. So I created a new kind of handle based on Dictionaries. The class is called Eco.ViewModel.Runtime.VMClass.

So having this ViewModel:


…you can use this in Silverlight (or WPF) like this:

   1: // UI and domain model so that you can clearly decouple them.
   2: // A ViewModel is a transformation of a part of the 
   3: // domain model where new attributes and associations are formed as transformations from
   4: // original ones in the domain model.
   5: ViewModelDefinitionsInApplication.Init(_ecoSpace);
   6: VMClass vm = ViewModelHelper.CreateFromViewModel("TestViewModel", _ecoSpace, _person);
   7: ViewModelStackPanel.DataContext = vm;

and the Xaml that goes with it shows how to formulate the Bindings towards the Dictionaries:

   1: <StackPanel x:Name="ViewModelStackPanel" Background="Purple">
   2:     <TextBlock Text="ViewModelBind without generated code">
   3:     </TextBlock>
   4:     <TextBlock Text="Dynamicobject would have been great for this ; but there is a bug -">
   5:     </TextBlock>
   6:     <TextBlock Text="It does NOT support binding in Silverlight :-)">
   7:     </TextBlock>
   8:     <TextBox Width="200" Height="20" Text="{Binding Path=[FirstName].Value, Mode=TwoWay}">
   9:     </TextBox>
  10:     <TextBlock Width="200" Height="20" Text="{Binding Path=[FullName].Value}">
  11:     </TextBlock>
  12:     <TextBox Width="200" Height="20" 
  13:                          Text="{Binding Path=[Home].Value.[AsString].Value, Mode=TwoWay}">
  14:     </TextBox>
  15:     <sdk:DataGrid x:Name="GridWithOwnedBuildings" Height="100" AutoGenerateColumns="False" 
  16:                          ItemsSource="{Binding Path=[OwnedBuildings]}" >
  17:         <sdk:DataGrid.Columns>
  18:             <sdk:DataGridTextColumn IsReadOnly="False" Header="Address" 
  19:                       Binding="{Binding [Address].Value,Mode=TwoWay}"></sdk:DataGridTextColumn>
  20:         </sdk:DataGrid.Columns>
  21:     </sdk:DataGrid>
  22:     <Button Click="Button_Click_2">
  23:         <TextBlock Text="Add another owned building"></TextBlock>
  24:     </Button>
  26:     <StackPanel DataContext="{Binding Path=SelectedItem,  ElementName=GridWithOwnedBuildings}"
  27:                                                                       Background="Green">
  28:         <TextBlock Text="Master detail hook to selected  OwnedBuildings SelectedItem">
  29:         </TextBlock>
  30:         <TextBox Width="200" Height="20" Text="{Binding Path=[Address].Value, Mode=TwoWay}">
  31:         </TextBox>
  32:         <Button Click="Button_Click_3">
  33:             <TextBlock Text="Remove Owned building from link"></TextBlock>
  34:         </Button>
  35:     </StackPanel>
  37: </StackPanel>

The Binding-expressions need to have the “[]” –square brackets around the ViewModelColumn names – this is the syntax of the xaml binding expression language to get into the dictionary. Even if this would be acceptable it still has one major drawback – design time binding support.

In Visual Studio 2010 we have been given design time support for bindings – Thank you! But the binding support works solely on reflection and of course the VMClass-Dictionaries are empty in design time and not considered when offering design time help for bindings.

So what we did was to accept this fact (if the mountain will not come to Muhammad, Muhammad must go to the mountain ). And the solution is to read the ViewModel definition and generate code for it. A new checkbox in the ViewModelEditor – you can check it to have code generated for that ViewModel:

Declarative ViewModel

Then when you Update the code you will get a new file in your model assembly:



And then you can go like this in your xaml:

   1: <UserControl xmlns:sdk=""  
   2:     x:Class="EcoSilverlightDemo.Page"
   3:     xmlns="" 
   4:     xmlns:x=""
   5:     xmlns:cgvm=
   6:       "clr-namespace:EcoSilverlightDemo.Model.ViewModelCodeGen_TestViewModel;assembly=EcoSilverlightDemo.Model"             
   7:     Width="500" >
   8:     <UserControl.Resources>
   9:         <cgvm:TestViewModel x:Key="CGVm1"></cgvm:TestViewModel>
  10:     </UserControl.Resources>
  11:     <Grid x:Name="LayoutRoot" Background="White">
  12:         <StackPanel Width="500">
  13:             <StackPanel x:Name="CodeGenStackPanel" DataContext="{StaticResource CGVm1}" >
  14:                 <TextBox Width="200" Height="20" Text="{Binding Path=Root.FirstName, Mode=TwoWay}">
  15:                 </TextBox>
  16:                 <TextBlock Width="200" Height="20" Text="{Binding Path=Root.FullName}">
  17:                 </TextBlock>
  18:                 <TextBox Width="200" Height="20" Text="{Binding Path=Root.Home.AsString, Mode=TwoWay}">
  19:                 </TextBox>
  20:                 <sdk:DataGrid x:Name="GridWithOwnedBuildings2" Height="100" 
  21:                         AutoGenerateColumns="False" ItemsSource="{Binding Path=Root.OwnedBuildings}" >
  22:                     <sdk:DataGrid.Columns>
  23:                         <sdk:DataGridTextColumn IsReadOnly="False" Header="Address" 
  24:                                  Binding="{Binding Address,Mode=TwoWay}"></sdk:DataGridTextColumn>
  25:                     </sdk:DataGrid.Columns>
  26:                 </sdk:DataGrid>
  27:             </StackPanel>            
  28:         </StackPanel>
  29:     </Grid>
  30: </UserControl>

Please note the rather longish namespace declaration: xmlns:cgvm="clr-namespace:EcoSilverlightDemo.Model.ViewModelCodeGen_TestViewModel;assembly=EcoSilverlightDemo.Model"

I needed to create a new namespace for each ViewModel in order to avoid name clashes with the domain model.

The generated ViewModel code gave us a way to explain statically what we want to bind to. The following resource definition is also noteworthy:

   1: <UserControl.Resources>
   2:     <cgvm:TestViewModel x:Key="CGVm1"></cgvm:TestViewModel>
   3: </UserControl.Resources>

Then we pick this up here:

   1: <StackPanel x:Name="CodeGenStackPanel" DataContext="{StaticResource CGVm1}" >

And all of a sudden we hear choirs of angels whenever we on focus something we want to bind to:

image   image

… and it turns out that the Angels are no Angels at all but just the soft humming of our cpu fan as it uses a few clock cycles to reflect our ViewModel-generated-code in order to give us detailed information about the available bindings. Lovely!

All we need to do in runtime is to hook up that ViewModel from the resource with an actual object and ecospace:

   1: TestViewModel testViewModel = Resources["CGVm1"] as TestViewModel;
   2: testViewModel.SetObject(_ecoSpace,_person);


Blogger Hans Karlsen said...

This is a link to the forum post with the build that is used for the article above:

July 31, 2010 at 7:08 PM  

Post a Comment

Subscribe to Post Comments [Atom]

<< Home

Contact Us | Terms of Use | Privacy Statement © 2009 CapableObjects