Saturday, 25 August 2012

Silverlight 4– MVVM – binding & dependency property injection

This article gives very brief introduction to MVVM (not in depth), there are loads of post about what it is and how it works in essence its splitting the code and UI into separate test driven layers within your application while trying to keep the business logic seperate from the UI.

To understand it better please see the simple example which shows one way you could bind your data.

One of this problems I hit very early on, was how do I use MVVM and allow data to be passed through from parent to child UserControls (these have to be generic - one for the address, one for a single phone number, etc) that are coded to validate themselves without having to understand the data context they are being used in. The big plan is to use them across multiple projects without having to rewrite/create each from scratch every time.

I started by dumping everything into one massive view model, this was the wrong way – it was nearly impossible to manage the code and with each control only needing the field(s) to do its single job it proved tough.

I looked at all the MVVM frameworks around, all are great, but none do all of the tasks I seemed to need. I also wanted to understand how MVVM worked in the background and using a framework would hide this from me.

No framework this time, I just coded my own simple modelBase to manage the standard bits and bobs.

What I ended up doing with the databinding is an MVVM application that uses dependency properties in the control code behind to manage data binding to some UI elements while still using MVVM to do the logic and control data storage.

I am building a Silverlight control to manage/create new customers so they can be vetted/contracts created etc for a telecoms company.

I have broken the form up into a number UserControls to try and make development easier and hopefully make each UserControl useable in other parts of the application.

The control forms a part of a larger control/view that manages an entire customer account, so being able to access the same customer data across (possibly) multiple Views is important.

The correct way to do this

I am using web services to build the objects/classes/observable collections, these are all stored in a set of static classes that are all run before the application loads the UI, I had issues with loading everything asynchronously as if the customer loaded before the list of categories for example it would crash (there is probably a work around for that).

The main page holds all user UserControls , each UserControl is populated with static data before I load the customer view and ViewModel which will set the selectedvalue on any databound controls or text property for text etc.

Injection into user control code behind;

I am adding a binding my Customer ViewModel object to the LayoutRoot grid’s DataContextProperty, however you could add this to any property on any control. Or you could add a single field from the DataContext as well to any UI property.

My child UserControl code behind =

namespace Project.Controls.Customer
{
    public partial class ContractDetails : UserControl
    {
        public ContractDetails()
        {
            InitializeComponent();
            LayoutRoot.SetBinding(Grid.DataContextProperty,
                  new System.Windows.Data.Binding
                  {
                      Source = this,
                      Path = new PropertyPath("PropName"),
                      Mode = System.Windows.Data.BindingMode.TwoWay   
                  });
        }

        public static DependencyProperty DataProperty =
                DependencyProperty.Register("PropName", typeof(GenericDataService.queueCustomer), typeof(ContractDetails), null);

        public GenericDataService.queueCustomer PropName
        {
            get { return (GenericDataService.queueCustomer)GetValue(DataProperty); }
            set
            {
                SetValue(DataProperty, value);
            }
        }
    }
}

I have set the Mode to TwoWay, you could set this to OneWay, OneTime or change this at runtime depending who who is logged to force the binding to be readonly.

I then pass the data into the child UserControl above in the xaml of the parent control;

   <my:ContractDetails  PropName="{Binding TheData }" x:Name="contractDetails1" />

One last step is to set the text property of the textbox that is on the child UserControl , in this case it is the phone number, I use a text filter command to only allow numbers and there will be some UK phone number validation as well.

    <TextBox Text="{Binding ElementName=LayoutRoot, Path=DataContext.PhoneNumber, Mode=TwoWay}" />

<TextBox Text="{Binding ElementName=LayoutRoot, Path=DataContext.Fax, Mode=TwoWay}" />

<ComboBox Height="23"  Name="CBContractDuration" Width="120" Grid.Column="2" ItemsSource="{Binding Path=ContractDuration, Source={StaticResource StaticData}, Mode=TwoWay}"  DisplayMemberPath="Name"   SelectedValuePath="id"  SelectedValue="{Binding ElementName=LayoutRoot, Path=DataContext.ContractDuration, Mode=TwoWay}"  />

From the ComboBox sample above you can see the control is filled with data form my Static data class, however the selected item is set by the Customer ViewModel data passed in through the dependency property up in the first example,

(I have tried to show the linking between the three steps by colouring the keywords, sorry if this is confusing for anyone)

It is important to remember that even in Xaml the binding keys are case sensitive  so in the above example PropName="{Binding TheData }"  is okay, but PropName="{Binding Thedata }"  will not work.

The above works well, but there are instances where you end up doing the validation on the control within the ChildUserControl in the code behind rather than the ViewModel, this is not perfect MVVM, but it works, so is good enough for me – at least until I work out how to do it all in the view model.

No comments :