How the Presentation Model could look like when using Silverlight 2.0 (Part 3)

In my previous Part 1 and Part 2 I wrote how a Presentation Model could be implemented and how to handle Async .calls. In this post I will show you how we can avoid adding code to the Code-behind. In Part 2, I had the following code in the code-behind:

public partial class Page : UserControl
{
   private NamePresentationModel _namePresentationModel;

   public Page()
   {
      InitializeComponent();

      _namePresentationModel = new NamePresentationModel(new NameRepository());

      this.Loaded += Page_Loaded;
   }

   void Page_Loaded(object sender, RoutedEventArgs e)
   {
       LoadFromPresentationModel();
   }

   private void Button_Click(object sender, RoutedEventArgs e)
   {
     _namePresentationModel.SaveAsync((source, args) =>
                                    {
                                       messageTextBlock.Text = "NameEntity Saved";
                                    });
   }


    private void LoadFromPresentationModel()
    {
       _namePresentationModel.LoadAsync((sender, args) =>
                                     {
                                          myStackPanel.DataContext = args.Result;
                                     });
    }
}


Wouldn’t it be nice if we can leave the code-behind empty and instead specify which Presentation Model we would like to use in the XAML file instead? Here is my new code-bind:

public partial class Page : View
{
    public Page()
    {
        InitializeComponent();
    }
}


I have moved all the actions and creation of the Presentation Model to the XAML file instead:

<e:View x:Class="PresentationModelExample1.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:e="clr-namespace:Silverlight.Extension;assembly=Silverlight.Extension"
    xmlns:m="clr-namespace:PresentationModelExample1.Model;assembly=PresentationModelExample1"
    Width="400" Height="300"
    e:Action.Loaded="Load">
    <e:View.Model>
        <m:NamePresentationModel/>
    </e:View.Model>

    <Grid x:Name="LayoutRoot" Background="White">
        <StackPanel Margin="50">

            <TextBox x:Name="name" Margin="4" Text="{Binding Name}"></TextBox>

            <Button e:Action.Click="Save" Margin="10" Content="Save"></Button>

        </StackPanel>
        
    </Grid>
</e:View>


By using the Action.<EventName> Attached Property, I can specify a method located in the Presentation Model and invoke it when the <EventName> is trigged. The View.Model Attached Property is used to specify which Presentation Model the View should use. Here is the code for the View class used instead of the UserControl:

public class View : UserControl
{
    public object Model
    {
        get { return DataContext as Model; }
        set { DataContext = value; }
    }
}


As you can see when I give the Model a value, the value will be bound to the DataContext of the UserControl. I need to change the code-behind to inherits from my View class, and I also need to add a namespace to the XAML file to point out where the View class is located. Then I replace the <UserControl> element with my new View element instead. When this is done I can use normal data binding to get values from my Presentation Model. The next thing to do is to create a Attached Property to make sure they invoke the specified Presentation Model methods when an event is trigged. It’s quite easy to add a Attached Property, the following example is the Action.Click attached property:

public static class Action
{

       public static readonly DependencyProperty ClickProperty =
          DependencyProperty.RegisterAttached(
             "Click",
             typeof(string),
             typeof(Action),
             new PropertyMetadata(
                 null,
                 new PropertyChangedCallback(OnClickChanged)));


       public static string GetClick(DependencyObject element)
       {
           return (string)element.GetValue(ClickProperty);
       }


       public static void SetClick(DependencyObject element, string value)
       {
           element.SetValue(ClickProperty, value);
       }


       private static void OnClickChanged(DependencyObject sourceElement,
                                          DependencyPropertyChangedEventArgs e)
       {
           var button = sourceElement as Button;

           if (button == null)
               throw new ArgumentException("DependencyObject is not of type Button");

           button.Click += ((sender, args) => { ActionHelper.GenerateActionCall(e.NewValue.ToString(), button); });
       }
}


We only need to create a static class and then add a DependencyProperty and use the DependencyProeprty’s RegisterAttached method to register a Attached Property. The argument are the name of the Property to use, the type, owner and then a PropertyChangedCallback which will be executed when the properties value are changed. Next step is to add a Get<PropertyName> and a Set<PropertyName> method and make sure it will set and get the value from the specified DependencyProperty. The name after the Get and Set prefix is the name of the property that you want to use. So if you name the Get method to GetMyPropery, the Attached property in the XAML will be Action.GetMyProperty.

In the specified callback method of the DependencyProperty, I have hooked up to the button control’s Click event. The ActionHelper class something that I have created to help me create a method which will invoke the specified action method. So the method will only get the Model from the DataContext of the specified control and then call the InvokeMember of the model’s type, something like this:

model.GetType().InvokeMember(
                    actionName,
                    System.Reflection.BindingFlags.InvokeMethod,
                    null,
                    model,
                    arguments);


I have also added some code to map arguments of the action method to controls on the UI, so if I have a Save method with an argument called name I will get the value from a control with the same name as the argument. If the argument of the Action method is an entity, I will create a new instance of  the Entity and map it’s property to controls with the same name.

public void Save(string name)
{
    //...
}

public void Save(NameEntity name)
{
    //...
    _nameRepository.SaveName(name);
}


There are several of frameworks created to map a Presentation Model to a View without using a code-behind. I only created my own to learn more about how to extend Silverlight elements etc. You can for example get a similar framework from Nikhil (Member of the ASP.Net team) called Silverlight.FX, another project with similar functionality is the Caliburn.

3 Comments

Comments have been disabled for this content.