Metro Data Binding

18 April 2012 (updated 29 May 2012)

Summary:
In this article we review briefly some of the data binding options available to XAML-based Metro apps.

Introduction

.NET data binding allows us to quickly develop applications where data can be automatically flowed from a source (e.g. an object, variable or UI control property) to a dependency property (e.g. a TextBox's Text property) in a target. It can also work in the opposite direction.

Windows Metro and WPF apps employ an architectural pattern known as Model-View-ViewModel (MVVM), with data binding being a core part of its practical implementation. In MVVM, the Model encapsulates business/data logic, the View is defined using XAML, and the ViewModel makes model data available to the view. The strict separation-of-concerns in MVVM means that a view can call the view-model, but not the model. Similarly, the model can't call the view directly (it shouldn't know anything about it). It should be noted that for simple apps, the model is often not separate entity, but forms part of the view-model.

In Metro and WPF apps, the interaction between view and view-model is often largely accomplished through the data binding and event systems, the goal being to minimize and simplify the amount of code required in the code-behind file. Indeed, a MVVM-based approach means that as far as possible, connections between the view and view-model are achieved through data bindings specified in XAML.

Note that, substantially, data binding in XAML-based Metro apps with .NET 4.5 is the same as for .NET 4.0 WPF apps. However, there are a few restrictions, such as connecting to databases, that we'll also cover.

Binding to Controls

Binding one control to some property of another control is simply achieved through XAML. For example, here we bind a TextBlock's Text property to the SelectedItem property of a ListBox:

  <ListBox x:Name="listBox1" 
           HorizontalAlignment="Left" 
           Height="120" 
           Margin="10,159,0,0" 
           VerticalAlignment="Top" 
           Width="149">
      <x:String>Item #1</x:String>
      <x:String>Item #2</x:String>
      <x:String>Item #3</x:String>
  </ListBox>

  <TextBlock x:Name="textBlock1" 
      HorizontalAlignment="Left" 
      Margin="10,284,0,0" 
      Text="{Binding ElementName=listBox1, Path=SelectedItem}" 
      VerticalAlignment="Top" 
      FontSize="20"
      Width="149"/>

When a string is selected in the ListBox, the TextBlock updates to display that item. Notice the form of the binding expression:

{Binding ElementName=source, Path=source-property}

Where, ElementName specifies the UI control to bind to (source) and Path=Selected specifies the property to bind to, in this case SelectedItem.

If the objects being displayed by the ListBox are not simple strings, we can easily specify a particular property of the object. For example, here we have a Person object containing Name and Address properties. We bind to the Person.Name property as follows:

  <TextBlock x:Name="textBlock1" 
      HorizontalAlignment="Left" 
      Margin="10,284,0,0" 
      Text="{Binding ElementName=listBox1, Path=SelectedItem.Name}" 
      VerticalAlignment="Top" 
      FontSize="20"
      Width="149"/>

Binding to Objects

If we have variables or properties in the code-behind file for a Metro-style Page (or external classes), we can bind UI controls to them as follow:

<Page
    :
    xmlns:local="using:ResourcesDemo"
    Name="myPage">  
    <!-- Notice how we give the page a name so we can reference 
             it by binding to Elementname=myPage -->

    <Page.Resources>
        <!-- Specify a class that we'll use to convert from strings to ints -->
        <local:MyInt2StringConverter x:Key="intConverter" />  

        <!-- Create an instance of the MyData class called myData for binding -->
        <local:MyData x:Key="myData" /> 
    </Page.Resources>
    :
    :
    <!-- Bind to a string property (AString) in BlankPage. 
         Needs to have myTextBlock.DataContext=this in code-behind -->
    <TextBlock Name="myTextBlock" FontSize="48" Text="{Binding Path=AString}" />

    <!-- Bind to a string property (AnotherString) in BlankPage object. 
               Notice the use of Elementname=myPage, which saves having to set the DataContext in code -->
    <TextBlock FontSize="48" Text="{Binding ElementName=myPage, Path=AnotherString}" />

    <!-- Bind to a number property in the BlankPage object. 
               Notice the use of Elementname=myPage, which saves having to set the DataContext in code
               Convert the number to a string using a custom convertor class that implements IValueConverter -->
    <TextBlock Name="tbInt2Str" 
               FontSize="48" 
               Text="{Binding ElementName=myPage, Path=ANumber, Converter={StaticResource intConverter}}" />         

    <!-- Bind to the ABigString property in the MyData class.
         Notice the use of Source={StaticResource myData} to specify the instance of the source -->
    <TextBlock Width="100" Height="60" Text="{Binding Source={StaticResource myData}, Path=ABigString}" />
    :
    :

The int-to-string converter class could be implemented like this:

 public sealed class MyInt2StringConverter : IValueConverter
 {
     public object Convert(
         object value, Type targetType, object parameter, string language)
     {
         return value.ToString();
     }

     public object ConvertBack(
         object value, Type targetType, object parameter, string language)
     {
         return int.Parse(value as string);
     }
 }

Binding to Collections

Suppose we have a collection of Person objects:

   public class Person 
   {
       private string _name;
       public string Name
       {
           get { return _name; }
           set { _name = value; }
       }

       private string _address;
       public string Address
       {
           get { return _address; }
           set { _address = value; }
       }

       public Person() {}
       public Person(string name, string adr)
       {
           Name = name;
           Address = adr;
       }

       public override string ToString() { return Name + ", " + Address; }
   }

We create the Person object collection at runtime, and bind the ListBox to the collection simply by setting its DataContext property as follows:

   public List<Person> Persons { get; set; }

   public BlankPage()
   {
       this.InitializeComponent();

       Persons = new List<Person>();
       Persons.Add(new Person("Fred Smith", "123 The Street"));
       Persons.Add(new Person("Mary Green", "456 The Avenue"));
       listBox1.DataContext = Persons;
   }

We then set the ListBox's ItemsSource property to be a binding expression:

  <ListBox x:Name="listBox1" 
           ItemsSource="{Binding}"
           HorizontalAlignment="Left" 
           Height="120" 
           Margin="10,159,0,0" 
           VerticalAlignment="Top" 
           Width="220">
  </ListBox>

Change Notifications - Collections

As currently implemented, our simple example has an issue: if we add items to the Person collection these additions are not reflected in the ListBox. The reason is that the binding target (the ListBox) has no way of knowing that a change has happened in the source collection.

The simplest way to fix this problem is to change the collection from a List<Person> to an ObservableCollection<Person>. ObservableCollection implements the INotifyCollectionChanged interface, this ensures that an event is fired when a change occurs in the collection, and allows the data binding mechanism to correctly update the displayed list:

   public ObservableCollection<Person> Persons { get; set; }

   public BlankPage()
   {
       this.InitializeComponent();

       Persons = new ObservableCollection<Person>();
       Persons.Add(new Person("Fred Smith", "123 The Street"));
       Persons.Add(new Person("Mary Green", "456 The Avenue"));
       listBox1.DataContext = Persons;
   }

Change Notifications - Objects

As another example, assume we want to edit a Person object (here we've bound two TextBox controls to the Name and Address properties of a single Person):

When the app is first run, we create a new Person object and set the TextBox controls DataContext property:

  public sealed partial class BlankPage1 : Page
  {
      public Person NewPerson { get; set; }

      public BlankPage1()
      {
          this.InitializeComponent();

          NewPerson = new Person("Nobody", "Nowhere");
          tbName.DataContext = NewPerson;
          tbAddress.DataContext = NewPerson;
      }

      private void btnEditPerson_Click(object sender, RoutedEventArgs e)
      {
          NewPerson.Name = "Fred Smith";
          NewPerson.Address = "123 The Street";
      }
  }

If we run the above code, we'll find that the TextBox controls correctly bind and show their initial values. However, when we try to update the values by clicking Edit Person, we find that the TextBox controls do not reflect the changes made.

As with collections, the reason the bindings don't show the updates is because there's no mechanism in-place to notify them that a change has been made to the source object. In order to successfully propagate the changes we need to change the Person class to implement the INotifyPropertyChanged interface and to fire the PropertyChanged event whenever a property is changed:

  public class Person : INotifyPropertyChanged 
  {
      public event PropertyChangedEventHandler PropertyChanged;
      private string _name;
      public string Name
      {
          get { return _name; }
          set { _name = value; NotifyPropertyChanged("Name"); }
      }

      private string m_address;
      public string Address
      {
          get { return _address; }
          set { _address = value; NotifyPropertyChanged("Address"); }
      }

      public Person() {}
      public Person(string name, string adr)
      {
          Name = name;
          Address = adr;
      }

      public override string ToString()
      {
          return Name + ", " + Address;
      }

                  private void NotifyPropertyChanged(string propertyChanged)
      {
          if (PropertyChanged != null)
              PropertyChanged(this, new PropertyChangedEventArgs(propertyChanged));
      }
  }

We can take this example a stage further. An alternative to implementing the INotifyPropertyChanged interface manually and firing the PropertyChanged event is to derive our Person class from BindableBase. This allows us to automatically fire the PropertyChanged event as required, and removes the need for error-prone calls to NotifyPropertyChanged("property name"). As all developers will know, any time a string literal's involved, there's potential for introducing errors through mistyping, copy & paste refactoring, etc.

The updated definition of the Person class now looks like this:

  // This an alternative to implementing INotifyPropertyChanged and manually 
  // calling OnPropertyChanged("property name") whenever a property changes
  public class Person : BindableBase
  {
      private string _name;
      public string Name
      {
          get { return _name; }
          set
          {
              if(SetProperty<string>(ref _name, value))
                  this._name = value;
          }
      }

      private string _address;
      public string Address
      {
          get { return _address; }
          set
          {
              if (SetProperty<string>(ref _address, value))
                  this._address = value;
          }
      }

      public Person() { }
      public Person(string name, string adr)
      {
          Name = name;
          Address = adr;
      }

      public override string ToString()
      {
          return Name + ", " + Address;
      }
  }

Binding to a JSON Source

Consuming JSON (JavaScript Object Notation) data sources is a common pattern for connected apps of all types. The following example shows how easy it is to make a direct WebRequest to a web server that hosts a JSON data source. In this case, the data is held in a static text file. We examine how to get JSON data via a WCF Web Service in a subsequent example.

First, here's the JSON data (it's designed to be mapped to the simple Person class we've used in previous examples):

  [
    {"Name":"Fred smith",  "Address":"1 The Street"},
    {"Name":"Mary Green",  "Address":"2 The Street"},
    {"Name":"Bob Black",   "Address":"3 The Street"},
    {"Name":"Alice White", "Address":"4 The Street"},
    {"Name":"Jim Brown",   "Address":"5 The Street"}, 
  ]

The UI for our app is as follows:

  <Grid Background="#FF6EC944">
      <Button x:Name="btnGetJsonData" 
              Content="Get JSON Data" 
              HorizontalAlignment="Left" 
              Margin="10,10,0,0" 
              VerticalAlignment="Top" 
              Width="215" 
              Click="btnGetJsonData_Click"/>
      
      <ListBox x:Name="lbJsonData" 
               ItemsSource="{Binding}" 
               HorizontalAlignment="Left" 
               Height="121" Margin="10,54,0,0"
               VerticalAlignment="Top" 
               Width="215"/>
      
      <TextBlock HorizontalAlignment="Left"
                 Margin="10,180,0,0" FontSize="20" 
                 Text="{Binding ElementName=lbJsonData, Path=SelectedItem.Name}" 
                 VerticalAlignment="Top" 
                 Width="215"/>

  </Grid>

Here's the updated definition of our Person class. Notice that we've added the [DataContract] attribute to the class, and the [DataMember] attribute to the properties. We'll see shortly how these attributes will help map incoming JSON data to the relevant property. We've also added a new static method named GetDataAsync() to the class:

  [DataContract]
  public class Person : BindableBase
  {
      private string _name;
      [DataMember]
      public string Name
      {
          get { return _name; }
          set
          {
              if(SetProperty<string>(ref _name, value))
                  this._name = value;
          }
      }

      private string _address;
      [DataMember]
      public string Address
      {
          get { return _address; }
          set
          {
              if (SetProperty<string>(ref _address, value))
                  this._address = value;
          }
      }

      public Person() { }
      public Person(string name, string adr)
      {
          Name = name;
          Address = adr;
      }

      public override string ToString()
      {
          return Name + ", " + Address;
      }

      public static async Task<ObservableCollection<Person>> GetDataAsync(string uri)
      {
          System.Runtime.Serialization.Json.DataContractJsonSerializer deserialize;
          ObservableCollection<Person> data = null;

          HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri(uri));
          request.Method = "GET";
          request.ContentType = "application/json";

          HttpWebResponse response = (HttpWebResponse)(await request.GetResponseAsync());
          if (response.StatusCode == HttpStatusCode.OK)
          {
              deserialize = new System.Runtime.Serialization.Json.DataContractJsonSerializer(
                  typeof(ObservableCollection<Person>));

              data = deserialize.ReadObject(response.GetResponseStream()) as
                  ObservableCollection<Person>;
          }

          return data;
      }
  }

The GetDataAsync() method makes an async WebRequest to retrieve the JSON data. Assuming the response is OK, the DataContractJsonSerializer class is used to read and deserialize the JSON data. As its name implies, this class makes use of the [DataContract] and [DataMember] attributes on the Person class to decode the JSON text into an ObservableCollection of Person objects, which is then returned.

The "Get JSON Data" button-click handler simply makes an async call to GetDataAsync() and then sets the ListBox's DataContext:

   private async void btnGetJsonData_Click(object sender, RoutedEventArgs e)
   {
       ObservableCollection<Person> data = 
           await Person.GetDataAsync("http://localhost/rarcher.net/jsonData.txt");

       lbJsonData.DataContext = data;
   }

Binding to a WCF Service

One of the by-design restrictions placed on Metro-style apps is that they cannot connect to external databases, either directly through a data source (the Tools | Add New Data Source wizard won't provide you with a 'Database' option, you can only create a data source from a Service or Object), or by using the Entity Framework. Note that you also can't reference the System.Data assembly.

The following example shows how to create a WCF service that returns an ObservableCollection<Person> via the Entity Framework.

First, create a new WCF Service Application project:

Delete the auto-generated service (Service1) and then create a new service called PersonService. Note that you can find more info on creating WCF services in one of my earlier posts.

Define the interface for the Person class as follows:

  [ServiceContract]
  public interface IPerson
  {
      [DataMember]
      string Name { get; set; }

      [DataMember]
      string Address { get; set; }

      [OperationContract]
      Task<ObservableCollection<Person>> GetPersonDataAsync();
  }

Then create the Person class:

  public class Person : IPerson
  {
      public string Name { get; set; }
      public string Address { get; set; }

      public Person() { }
      public Person(string name, string address) 
      {
          this.Name = name;
          this.Address = address;
      }

      public async Task<ObservableCollection<Person>> GetPersonDataAsync()
      {
          return null;
      }
  }

From Server Explorer, right-click Data Connections and select Create new SQL Server Database. Create a new table called Persons and add two columns: Name and Address. Add a few rows of example data:

Now right-click the project in Solution Explorer and select Add New Item, (Data) ADO.NET Entity data Model. Name the model PersonsDataModel:

In the Entity Data Model Wizard, choose Generate from Database. Select Persons as the database and then make sure the Persons table is selected. Name the model namespace PersonsDataModel:

Visual Studio generates the entity model code. When the Person entity diagram is displayed, be sure to rename the Person entity to PersonModel. If you don't you'll get compilation errors as we already have a class named Person:

We can now complete the code for our GetPersonDataAsync() method:

  // Add for entity framework...
  using System.Data.Objects;
  using System.Data.Objects.DataClasses;
  :
  :
  public async Task<ObservableCollection<Person>> GetPersonDataAsync()
  {
      PersonsEntities personsEDM = new PersonsEntities();
      var query = from obj in personsEDM.Persons select obj;
      var results = ((ObjectQuery)query).Execute(MergeOption.AppendOnly);  // Get the data

      // Normally, UI controls can bind directly to the ObjectResult returned from the query Execute().
      // However, Metro apps can't (by design) reference databases or make use of the Entity Framework.
      // So, we first have to convert the collection of EDM PersonModel to an Observable collection
      // of Person...

      ObservableCollection<Person> data = new ObservableCollection<Person>();
      await Task.Run(() =>
      {
          foreach (PersonModel pm in results)
              data.Add(new Person(pm.Name, pm.Address));
      });

      return data;
  }

To test the service, select Person.svc in Solution Explorer and then start debugging the project. You should see the WCF Test Client displayed. Double-click the GetPersonDataAsync() method and then click the Invoke button to call it:

We can now deploy our service to IIS (you can find more info on deploying WCF services in one of my earlier posts). Using the IIS management console, simply create an Application, give it an alias of PersonService and set the physical path to the directory where the service files are located. Now right-click the newly created application and select Edit Permissions. Make sure that either the IIS_IUSRS or Everybody account has Read & execute, List folder contents and Read permissions on the folder containing the service.

Switch to Content View, right-click Person.svc and select Browse. You should now see your browser start and display the following page:

Copy the URL of the service, we'll need in the next step when we create a Metro client application.

We're now ready to create the Metro client that will bind to the service. Create a new BlankPage-based Metro app and add a ListBox and a Button:

  <Grid Background="#FFD68534">
      <Button x:Name="btnGetData" 
              Content="Get data from WCF service" 
              HorizontalAlignment="Left" 
              Margin="10,10,0,0" 
              VerticalAlignment="Top" 
              Width="227" Click="btnGetData_Click"/>
      
      <ListBox x:Name="lbPersondata" HorizontalAlignment="Left" 
               ItemsSource="{Binding}"
               DisplayMemberPath="Name"
               Height="137" 
               Margin="10,54,0,0" 
               VerticalAlignment="Top" 
               Width="227"/>
  </Grid>

Add a Service Reference, using the service URL we saved earlier. Now wire-up the button click handler to get the data from the service and setup data binding:

  using System.Threading.Tasks;
  using System.Net;
  using System.Net.Http;
  using System.ServiceModel;
  using System.Runtime.Serialization;
  using System.Collections.ObjectModel;
  
  :
  :
  private async void btnGetData_Click(object sender, RoutedEventArgs e)
  {
      PersonService.PersonClient service = new PersonService.PersonClient();
      ObservableCollection<PersonService.Person> data = await service.GetPersonDataAsync();
      lbPersondata.DataContext = data;
  }

Summary:
In this article we reviewed some of the data binding options available to XAML-based Metro apps. We saw how to bind controls to other controls, objects and collections. We also reviewed how to bind to JSON and WCF Service data.