iPhone to WP7 – Diving Deeper

i2WLogo2-Tutorials

Go to First Tutorial

In this tutorial, we’ll expand upon the work done in the previous tutorial and we’ll look more closely at a few key issues.

MVVM

iStock_WindowXSmall As an iPhone Developer your thinking is adapted to and influenced by the Model-View-Controller pattern. The very good news is that the pattern for Windows Phone 7 (WP7) development, MVVM, is very closely related to MVC.


Wikipedia notes that MVVM evolved at Microsoft out of the Model-View-Presenter pattern developed by Martin Fowlerwhich was based, in turn on MVC.  The originators of MVVM often refer to it as a specialization of Model-View-Presenter.   Wikipedia goes on to say,

Largely based on the Model-view-controller pattern (MVC), MVVM is targeted at modern UI development platforms… in which there is a User Experience (UX) developer who has different requirements than a more “traditional” developer (i.e. oriented toward business logic and back end development). The View-Model of MVVM is “basically a value converter on steroids” meaning that the View-Model is responsible for exposing the data objects from the Model in such a way that those objects are easily managed and consumed. In this respect, the View-Model is more Model than View, and handles most if not all of the View’s display logic (though the demarcation between what functions are handled by which layer is a subject of ongoing discussion and exploration).

It is important to understand that MVVM and Silverlight co-evolved, each influencing the other, and thus the “hand in glove” fit between them is no coincidence.

In MVVM both the View and the Model are identical in meaning to what they mean in MVC

The ViewModel is the Model of the View, that is, it is an abstraction of the view and, most important, (just about) all the user-generated view code is kept in the view model (rather than in the code-behind for the View as can also be done in WP7 programming).  Numerous writers have commented that the VM is really a specialized controller that acts as a data binder and converter; changing Model information in to View information, and passing commands from the View that may affect the Model.

Three Core Concepts

We’ve been talking about n-tier development, decoupling, scoping, visibility and related topics since at least 1990. I’m pretty sure that when they were cracking the Enigma machine in World War II, they discussed decoupling the code-breaker module from the UI (did they have UI then?)

MVVM, at its heart has three core concepts:

MVVMSketch

Core Concept #1Separate your User Interface concerns (View) from your Business objects and behaviors (View Model) and from your data/persistence layer (Model).

Core Concept #2: Don’t Look Up.

We tend to conceptualize the View at the top, the ViewModel  in the middle and the model  at the bottom.  The View can know about the ViewModel, the ViewModel about the Model, and the Model, like the cheese, stands alone.

Core Concept #3 – And this is the killer: Binding.

In MVVM the mechanism for the ViewModel to provide data to the View, is for the View to set the ViewModel as its DataContext.  That is so cool; it takes a while to realize the implications.  Further, we don’t just bind data, as I’ll show below.

[ click on any image to see it full size ]

Why Would You Want To Do That, & What Does It Cost?

The huge advantages of MVVM (and MVC) are that

  • you write less code
  • you have enormously increased the testability of your application.

It is a bear to try to test a UI object because the pesky UI gets in the way. ViewModels have no UI, they have just the things you want to test: “does it behave as expected? and is the data correct at any given instant?”

So, the cost is negative; that is, by adopting MVVM you don’t work harder, you work less, and in exchange for doing less, your code is easier to write, to read, to maintain and to test. Not bad.

Concrete Guidelines

Because MVVM is somewhat new, and because I’m totally disinterested in pilpul I will arbitrarily suggest the following guidelines:

  1. Do not bother with MVVM for any program that takes less than 1 hour to write.
  2. Move as much logic as possible from the view to the VM, but try not to become obsessed
  3. Use a MVVM library (life is short!)   We’ll use the (free) MVVM Light Toolkit in this series.
  4. Chillax

Creating An MVVM Application

Let’s return to the application started in the previous tutorial, and add two-way binding so that we can update the records. That will give us an opportunity to examine, in a bit more detail, the responsibilities of the Model, ViewModel and View, and will allow us also to talk a bit more about event handling.

Because we are early in the tutorials, there is value in starting from scratch.  Create a new project of type MVVMLight (WP7) [If you don’t see this option you do not have the  MVVM Light Toolkit installed.].  Call your new application i2w_CustomerData

Here, for your cut and past convenience, is the Xaml from the example in the previous tutorial, except that I’ve removed the bottom two rows (Notes and Male/Female) and added a button (Next)

<Grid.RowDefinitions>
   <RowDefinition Height="1*" />
   <RowDefinition Height="1*" />
   <RowDefinition Height="1*" />
   <RowDefinition Height="1*" />
   <RowDefinition Height="1*" />
   <RowDefinition Height="1*" />
   <RowDefinition Height="1*" />
   <RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
   <ColumnDefinition Width="1*" />
   <ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.ColumnSpan="1"
           Grid.RowSpan="1"
           Height="30"
           HorizontalAlignment="Right"
           Margin="5"
           Name="NamePrompt"
           Text="Full Name"
           VerticalAlignment="Center" />
<TextBlock Height="30"
           HorizontalAlignment="Right"
           Margin="5"
           Name="AddressPrompt"
           Text="Street Address"
           VerticalAlignment="Center"
           Grid.Row="1" />
<TextBlock Height="30"
           HorizontalAlignment="Right"
           Margin="5"
           Name="CityStateZipPrompt"
           Text="City, State, Zip"
           VerticalAlignment="Center"
           Grid.Row="2" />
<TextBlock Height="30"
           HorizontalAlignment="Right"
           Margin="5"
           Name="PhonePrompt"
           Text="Phone"
           VerticalAlignment="Center"
           FontWeight="Bold"
           Grid.Row="3" />
<TextBlock FontWeight="Bold"
           Height="30"
           HorizontalAlignment="Right"
           Name="FaxPrompt"
           Text="Fax"
           VerticalAlignment="Center"
           Margin="5"
           Grid.Row="4" />
<TextBlock FontWeight="Bold"
           Height="30"
           HorizontalAlignment="Right"
           Name="EmailPrompt"
           Text="Email"
           VerticalAlignment="Center"
           Margin="5"
           Grid.Row="5" />
<TextBlock FontWeight="Bold"
           Height="30"
           HorizontalAlignment="Right"
           Name="NotesPrompt"
           Text="Notes"
           VerticalAlignment="Center"
           Margin="5"
           Grid.Row="7" />
<TextBox Grid.Column="1"
         Height="70"
         HorizontalAlignment="Left"
         Margin="5,0,0,5"
         Name="FullName"
         VerticalAlignment="Bottom"
         Width="303"
         Text="{Binding Name}" />
<TextBox Height="70"
         HorizontalAlignment="Left"
         Margin="5,0,0,5"
         Name="Address"
         VerticalAlignment="Bottom"
         Width="303"
         Grid.Column="1"
         Grid.Row="1"
         Text="{Binding Address}" />
<StackPanel Grid.Column="1"
            Grid.Row="2"
            Grid.RowSpan="1"
            HorizontalAlignment="Stretch"
            Name="cityStateZipStack"
            VerticalAlignment="Stretch"
            Orientation="Horizontal">
   <TextBox Height="70"
            Name="City"
            Width="150"
            HorizontalAlignment="Left"
            VerticalAlignment="Bottom"
            Margin="5,0,0,5"
            Text="{Binding City}" />
   <TextBox Height="70"
            Name="State"
            Width="74"
            HorizontalAlignment="Left"
            VerticalAlignment="Bottom"
            Margin="0,0,0,5"
            Text="{Binding State}"
 />
   <TextBox Height="70"
            Name="Zip"
            Width="93"
            Margin="0,0,0,5"
            Text="{Binding Zip}" />
</StackPanel>
<TextBox Height="70"
         HorizontalAlignment="Left"
         Margin="5,0,0,5"
         Name="Phone"
         VerticalAlignment="Bottom"
         Width="303"
         Grid.Column="1"
         Grid.Row="3"
         Text="{Binding WorkPhone}" />
<TextBox Height="70"
         HorizontalAlignment="Left"
         Margin="5,0,0,5"
         Name="Fax"
         VerticalAlignment="Bottom"
         Width="303"
         Grid.Column="1"
         Grid.Row="4"
         Text="{Binding Fax}" />
<TextBox Height="70"
         HorizontalAlignment="Left"
         Margin="5,0,0,5"
         Name="Email"
         VerticalAlignment="Bottom"
         Width="303"
         Grid.Column="1"
         Grid.Row="5"
         Text="{Binding WorkEmail}" />

<Button
         Name="Next"
         Content="Next"
         Width="150"
         Grid.Column="1"
         Grid.Row="7"
         />

Creating the Model

Once again, we’ll create a Customer.cs class in the model,

 public class Customer
 {

     public string First { get; set; }
     public string Last { get; set; }
     public string Address { get; set; }
     public string City { get; set; }
     public string State { get; set; }
     public string Zip { get; set; }
     public string HomePhone { get; set; }
     public string WorkPhone { get; set; }
     public string Fax { get; set; }
     public string WorkEmail { get; set; }
     public string HomeEmail { get; set; }
     public bool IsMale { get; set; }
     public string Notes { get; set; }
     public int CreditRating { get; set; }
     public DateTime FirstContact { get; set; }
     public DateTime LastContact { get; set; }

     public Customer(
         string first,
         string last,
         string address,
         string city,
         string state,
         string zip,
         string homePhone,
         string workPhone,
         string fax,
         string workEmail,
         string homeEmail,
         bool isMale,
         string notes,
         Int16 creditRating,
         DateTime firstContact,
         DateTime lastContact)
     {
         First = first;
         Last = last;
         Address = address;
         City = city;
         State = state;
         Zip = zip;
         HomePhone = homePhone;
         WorkPhone = workPhone;
         Fax = fax;
         HomeEmail = homeEmail;
         WorkEmail = workEmail;
         IsMale = isMale;
         Notes = notes;
         CreditRating = creditRating;
         FirstContact = firstContact;
         LastContact = lastContact;
     }
}

This time, however, let’s add to the model and create a collection of customers,

public class CustomerCollection
{
    private readonly List< Customer > _customers = new List< Customer >();

    public CustomerCollection()
    {
        GenerateCustomers( 500 );
    }

    public List< Customer > Customers
    {
        get { return _customers; }
    }

    public void GenerateCustomers( int howMany )
    {
        var firsts = new List< String >
                     {
                         "Abe",
                         "Alice",
                         "Barry",
                         "Basha",
                         "Charlie",
                         "Colette",
                         "David",
                         "Davida",
                         "Edgar",
                         "Elizabeth",
                         "Frank",
                         "Fran",
                         "George",
                         "Gary",
                         "Harry",
                         "Isaac",
                         "Jesse",
                         "Jessica",
                         "Kevin",
                         "Katrina",
                         "Larry",
                         "Linda",
                         "Mark",
                         "Melinda",
                         "Nick",
                         "Nancy",
                         "Oscar",
                         "Ophilia",
                         "Peter",
                         "Patricia",
                         "Quince",
                         "Quintina",
                         "Robert",
                         "Roberta",
                         "Shy",
                         "Sarah",
                         "Tom",
                         "Teri",
                         "Uberto",
                         "Uma",
                         "Victor",
                         "Victoria",
                         "Walter",
                         "Wendy",
                         "Xerxes",
                         "Xena",
                         "Yaakov",
                         "Yakira",
                         "Zach",
                         "Zahara"
                     };

        var lasts = new List< String >
                    {
                        "Anderson",
                        "Baker",
                        "Connors",
                        "Davidson",
                        "Edgecumbe",
                        "Franklin",
                        "Gregory",
                        "Hines",
                        "Isaacson",
                        "Johnson",
                        "Kennedy",
                        "Liberty",
                        "Mann",
                        "Nickerson",
                        "O'Dwyer",
                        "Patterson",
                        "Quimby",
                        "Richardson",
                        "Stevenson",
                        "Tino",
                        "Usher",
                        "Van Dam",
                        "Walker",
                        "Xenason",
                        "Yager",
                        "Zachery"
                    };
        var streets = new List< String >
                      {
                          "Ash",
                          "Beech",
                          "Cedar",
                          "Dogwood",
                          "Elm",
                          "Filbert",
                          "Ginkgo",
                          "Hawtorn",
                          "Ironwood",
                          "Juniper",
                          "Katsura",
                          "Lilac",
                          "Magnolia",
                          "Nectarine",
                          "Oak",
                          "Palm",
                          "Quince",
                          "Redwood",
                          "Sassafrass",
                          "Tupelo",
                          "Vibrunum",
                          "Walnut",
                          "Yellowwood",
                          "Zelkova"
                      };
        var cities = new List< String >
                     {
                         "Acton",
                         "Boston",
                         "Canton",
                         "Dell",
                         "Everstone",
                         "Flintwood",
                         "Gary",
                         "Houston",
                         "Imperial",
                         "Jackson",
                         "Kalamazoo",
                         "Levinworth",
                         "Macon",
                         "New York",
                         "Oak Hill",
                         "Paducah",
                         "Quinzy",
                         "Rochester",
                         "South Falls",
                         "Terra Haute",
                         "Union",
                         "Victoria",
                         "Waipio",
                         "Xenia",
                         "York",
                         "Zanesville"
                     };
        var isp = new List< String >
                  {
                      "ATT",
                      "Verizon",
                      "Hotmail",
                      "Gmail",
                      "Sprintnet",
                      "Yahoo"
                  };
        var states = new List< String >
                     {
                         "AL",
                         "AK",
                         "AS",
                         "AR",
                         "CA",
                         "CO",
                         "CT",
                         "DE",
                         "FL",
                         "GA",
                         "HI",
                         "ID",
                         "IL",
                         "IN",
                         "IA",
                         "KS",
                         "KY",
                         "LA",
                         "ME",
                         "MD",
                         "MA",
                         "MI",
                         "MN",
                         "MS",
                         "MO",
                         "MT",
                         "NE",
                         "NV",
                         "NH",
                         "NJ",
                         "NM",
                         "NY",
                         "NC",
                         "ND",
                         "OH",
                         "OK",
                         "PA",
                         "RI",
                         "SC",
                         "SD",
                         "TN",
                         "TX",
                         "UT",
                         "VA",
                         "WA",
                         "WI",
                         "WY"
                     };

        var random = new Random();
        for ( int i = 0; i < howMany; i++ )
        {
            string first = firsts[ random.Next( 0, firsts.Count ) ];
            string last = lasts[ random.Next( 0, lasts.Count ) ];
            int streetNumberInt = random.Next( 101, 999 );
            string streetNumber = streetNumberInt.ToString();
            string zipCode = random.Next( 10000, 99999 ).ToString();
            string homePhone = random.Next( 200, 999 ) + "-555-"
                               + random.Next( 2000, 9099 );
            string workPhone = random.Next( 200, 999 ) + "-555-"
                               + random.Next( 2000, 9099 );
            string fax = random.Next( 200, 999 ) + "-555-"
                         + random.Next( 2000, 9099 );
            string homeEmail = first + "@"
                               + isp[ random.Next( 0, isp.Count ) ]
                               + ".com";
            string workEmail = last + "@"
                               + isp[ random.Next( 0, isp.Count ) ]
                               + ".com";
            bool isMale = ( random.Next( 1, 3 ) ) % 2 == 0
                              ? true
                              : false;
            var creditRating = ( short ) random.Next( 100, 800 );
            var firstContact = new DateTime( 2010, 1, 1 );
            DateTime lastContact = DateTime.Now;

            _customers.Add(
                           new Customer(
                               first,
                               last,
                               streetNumber + " "
                               +
                               streets[
                                       random.Next(
                                                   0, streets.Count - 1 )
                                   ]
                               + " Street",
                               cities[
                                      random.Next( 0, cities.Count - 1 )
                                   ],
                               states[
                                      random.Next( 0, states.Count - 1 )
                                   ],
                               zipCode,
                               workPhone,
                               homePhone,
                               fax,
                               homeEmail,
                               workEmail,
                               isMale,
                               "No notes at this time",
                               creditRating,
                               firstContact,
                               lastContact ) );
        }
    }
}

The constructor of this class generates 500 random records and places them in a List<Customer>. the class consists of a public property, Customers, that returns the collection, and otherwise nothing but the method, GenerateCustomers, which mixes and matches names, cities, etc. to create records.

Data

It is tempting to create a search function and an update, but that complexity can wait for a later tutorial. Let’s keep things simple and just display the records one after the other.

The work of controlling the View is done in the ViewModel which will implement the INotifyPropertyChanged Interface.

Interfaces

An Interface is a contract that a class implements.  The Interface itself is declared with members, much like a class, but the members are not implemented. The Interface dictates what an implementing class will provide, and clients of the implementing class can count on those elements being present.  In this example, the binding mechanism depends on the VM

How You implement An Interface

You fulfill the contract by implementing all the methods and events of the Interface. That is, if the interface looks like this:

interface Writeable
{
   void Read();
   void Write();
}

(and notice that the interface does not have to begin with a capital I, though just about every interface from Microsoft does: INotifyProopertyChanged, IEnumerable, IClaudius…)

There is no implementation in the interface itself (nor does it have a return value or a public/private designation).  If your class states that it imlements this interface3, it must supply a body for both methods,

class foo : Writeable
{
   public void Think()
   {
      System.DialogBox.Show("I Think Therefore I Might Be");
    }

   public void Read()
   {
         System.DialogBox.Show("So Many Books, So Little Time.");
   }

   public void Write()
    {
       System.DialogBox.Show("To err is human, to write, divine.");
     }
}

The implementing class is of course free to add additional methods and events.

For more on delegates please see the C# documentation or my books Programming C# and Learning C#.

In this case, the INotifyPropertyChanged interface has only one member, the event PropertyChanged.  While it is up to you how you implement this event, you must follow its declaration, passing in an object and an instance of PropertyChangedEventArgs. The PropertyChangedEventArgs class has one member that you must provide: the name of the property that has changed. In our case, we want to say that every property has changed rather than sort through which properties are different in the new record vs. the old.

How You Declare the Interface

Add the interface you wish to declare you implement after declaring the class from which you derive:

public class MainViewModel : ViewModelBase, INotifyPropertyChanged

Implementing the View Model

The ViewModel in this case derives from ViewModelBase supplied by the MVVM Light Toolkit, which also implements INotifyPropertyChanged, so the declaration shown above is legal but redundant.

The base class follows the common idiom and implements a method RaisePropertyChanged that takes a property name and passes it along to the event (after doing some housekeeping that need not concern us now).

// MainViewModel.cs
public void Next()
 {
     _currentCustomer = _customerCollection.Customers[++_currentOffset];
     base.RaisePropertyChanged("Name");

When the event is called, the named property updates, using the data context in the view code behind,

// MainPage.xaml.cs
void MainPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
 {
     vm = new MainViewModel();
     DataContext = vm;

In our case, the data context is the ViewModel itself.  That works because the VM has properties for each of the controls on the page,

//MainViewModel.cs
 public string Address { get { return _currentCustomer.Address; } }
 public string City { get { return _currentCustomer.City;  } }
 public string State { get { return _currentCustomer.State; } }
 public string Zip { get { return _currentCustomer.Zip; } }
 public string WorkPhone { get { return _currentCustomer.WorkPhone; } }
 public string Fax { get { return _currentCustomer.Fax; } }
 public string WorkEmail { get { return _currentCustomer.WorkEmail; } }
 public string Name { get { return _currentCustomer.First + " " + _currentCustomer.Last; } }

Notice that this time we’re wrapping all the properties rather than using a pass through to the customer collection.

Here is the complete code for the ViewModel,

using GalaSoft.MvvmLight;
using i2W_CustomerData.Model;

namespace i2W_CustomerData.ViewModel
{
    public class MainViewModel : ViewModelBase
    {
        private int _currentOffset = -1;
        private readonly CustomerCollection _customerCollection = new CustomerCollection();

        public string Address { get { return _currentCustomer.Address; } }
        public string City { get { return _currentCustomer.City; } }
        public string State { get { return _currentCustomer.State; } }
        public string Zip { get { return _currentCustomer.Zip; } }
        public string WorkPhone { get { return _currentCustomer.WorkPhone; } }
        public string Fax { get { return _currentCustomer.Fax; } }
        public string WorkEmail { get { return _currentCustomer.WorkEmail; } }
        public string Name { get { return _currentCustomer.First + " " + _currentCustomer.Last; } }

        private Customer _currentCustomer;
        public Customer CurrentCustomer
        {
            get { return _currentCustomer; }
        }

        public string ApplicationTitle { get { return "iPhone To WP7"; } }

        public string PageName { get { return "Customer"; } }

        public MainViewModel()
        {
            _currentCustomer = new Customer();
            Next();
        }

        public void Next()
        {
            _currentCustomer = _customerCollection.Customers[++_currentOffset];
            base.RaisePropertyChanged("Name");
            base.RaisePropertyChanged("Address");
            base.RaisePropertyChanged("City");
            base.RaisePropertyChanged("State");
            base.RaisePropertyChanged("Zip");
            base.RaisePropertyChanged("WorkPhone");
            base.RaisePropertyChanged("Fax");
            base.RaisePropertyChanged("WorkEmail");
        }
    }
}

And, finally, here is the complete source for the code behind for the MainPage.xaml

using i2W_CustomerData.ViewModel;

namespace i2W_CustomerData
{
    public partial class MainPage
    {
        private MainViewModel vm;

        public MainPage()
        {
            InitializeComponent();
            Loaded += MainPage_Loaded;
        }

        void MainPage_Loaded(
             object sender, System.Windows.RoutedEventArgs e)
        {
            vm = new MainViewModel();
            DataContext = vm;
            Next.Click += NextClick;
        }

        void NextClick(
           object sender, System.Windows.RoutedEventArgs e)
        {
            vm.Next();
        }
    }
}

Before we wrap this up, let’s focus in on the keyword event.  As an iPhone programmer, there is an intuitive, almost visceral sense of what an event is, but the keyword has a very specific meaning in C#.  To understand that, we have to back up and look at delegates.

Event Handling and Delegates

Obama When a head of state dies, the president of the United States typically doesn’t have time to attend the funeral personally. Instead, he dispatches a delegate. Often this delegate is the vice president, but sometimes the VP is unavailable and the president must send someone else, such as the secretary of state or even the “first lady.” He doesn’t want to “hardwire” his delegated authority to a single person; he might delegate this responsibility to anyone who is able to execute the correct funeral-protocol. The president defines in advance what responsibility will be delegated (attend the funeral), what parameters will be passed (condolences, kind words, warnings to potential usurpers), and what value he hopes to get back (good will, oil). He then assigns a particular person to that delegated responsibility at “runtime” as the course of his presidency progresses.

Some of the material in this article was stolen borrowed adapted from Programming C# 5th Edition.

[Note, I’m a big fan of our president, this picture is offered with affection. It is a small, articulated action figure given to me for my last birthday, taken in front of an intentionally soft image of the capital ]

I Made The Button, You Handle What Happens When It Is Clicked

The programmer who creates a control such as a button often will not be the programmer who puts that button into an application.  The control writer defines what events the control will support (click, selection changed, etc.) and the control user defines what happens for the events the control user is interested in responding to. Note that the control user need not react to every event, but on the other hand, adding a button that doesn’t react to being pressed is frowned upon. The way the control user indicates what is to happen is to write a method that will handle the event, and then tell the event which method to call when that event is “fired.”  It does that with delegates.

In C#, as in Cocoa Touch, delegates have the responsibility for taking action on behalf of other objects.  In C# however, delegates are created within a class, and that class is not the delegate.

Delegates

Technically, a delegate is a reference type used to encapsulate a method with a specific signature and return type (and even more technically, the return type is not part of the signature of a method).

You can encapsulate any matching method in that delegate.

An event is just a delegate that has been constrained in a few useful ways. For example, while a delegate can be used to invoke a method anywhere, events can only be invoked by the object that declares them (that is to say, if the button has a click event, only the button can raise that click event.)

Previous Tutorial Next Tutorial

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in Essentials, iPhoneToWP7, Mini-Tutorial, WindowsPhone and tagged , , , , , . Bookmark the permalink.

120 Responses to iPhone to WP7 – Diving Deeper

Comments are closed.