Random programming things I'd want to remember

Friday, February 28, 2014

Another simple MVVM tutorial for Windows Phone explained

I will take another attempt to explain the use of MVVM on Windows Phone. I already tried it before, but this time code is available on GitHub. Clone the project and follow along with my explanations.

This time we are building a registration database. The app will be tracking people's names along with gender.

1. Model

There is a class called "Person" in the Models folder. For the sake of simplicity, the app will track the name and gender.

2. View

There is MainPage.xaml file in ViewModels folder that is the front-end of the app (to change startup page for a Windows Phone app, go to Properties/WMAppManifest.xml and change it in "Navigation Page" field to whatever you need; the sample app already has it set up properly). There is a textbox for name, and radiobuttons to select a gender. The app must only allow to register when a name is provided and a gender is selected. So it can either provide feedback to the user when not all data is supplied for registration, or we can disable the Register button until all data is present.

There is a tricky part here. TextBox's text property originally had the following binding:

Text="{Binding UserName, Mode=TwoWay}"
The problem with that is that the Text property is updated in the view model only after textbox loses focus. To mitigate the situation, we can use the solution provided by Charles Petzold in his "Programming Windows Phone 7" book:
<!-- TextBox full declaration in XAML -->
<TextBox Text="{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=Explicit}" 
                     x:Name="boundTextBox" 
                     TextChanged="boundTextBox_TextChanged" />

//and then, the event in the code-behind file for the View:
private void boundTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
  TextBox txtbox = sender as TextBox;
  BindingExpression bindingExpression = txtbox.GetBindingExpression(TextBox.TextProperty);
  bindingExpression.UpdateSource();
}

As a result of this modification, every time a key is pressed in that TextBox, the property in our view model will be updated. One can argue that the true MVVM has no data in the code-behind files and this event might violate that principle, but the reality is that sometimes it's not easily possible. In addition, one of the main benefits of MVVM is testability. Having such an event in the code-behind of the view only affects internal workings of the app and does not affect data or business logic, so testability is very much preserved intact.

Now let's look a little closer at bindings. RegisterViewModel exposes properties and commands, which the view uses to display data or react to user input. For example, RegisterViewModel exposes a property named ProspectiveRegistration, which it calculates as as combination of name and gender:

public string ProspectiveRegistration
{
    get
    {
        string ret = this.UserName;
        ret += this.UserName.Length > 0 ? ", " : "";

        if (this.IsALady)
            ret += "Lady";
        if (this.IsAGentleman)
            ret += "Gentleman";

        return ret;
    }
}
The view binds this property to a TextBlock's Text property in the following manner:
<TextBlock Text="{Binding ProspectiveRegistration}" />
How does the view know that the property was updated? The answer to that lies in the setters of the all the other properties used in ProspectiveRegistration (UserName, IsALady, IsAGentleman). They all have the following line of code:
this.OnPropertyChanged("ProspectiveRegistration");
This line of code is the magic that updates the view with the new value of the property.

The example above was somewhat simple because the view only displayed the value of a property that belongs to the view model. What if the view needs to send data back to the ViewModel? Let's look at how the gender radiobuttons are declared:

<RadioButton Content="Lady" IsChecked="{Binding IsALady, Mode=TwoWay}" GroupName="genderGroup" />
IsChecked property is declared as a two-way property, and not only it displays the state of the property in the view model, but it also sends its value back to the view model. This is one of the ways how the view propagates data to the view model.

3. ViewModel

The view model for the project is the class called RegisterViewModel and it resides in the ViewModels folder along with the class called ViewModel. ViewModel class is the parent class for RegisterViewModel and it has the plumbing for INotifyPropertyChanged interface. It also has plumbing for navigation since it may be a bit tricky if using MVVM, but there are usage instructions.

RegisterViewModel.cs

A couple of interesting points here. First, the data store is an ObservableCollection. The difference between an ObservableCollection and List is that ObservableCollection lets know when it changed. The app conveniently subscribes to the CollectionChanged event to update properties bound to the view.

Next, let's look at the register command. Declaration:

private DelegateCommand _registerCommand;
It is an instance of a DelegateCommand. What is a DelegateCommand? Any command bound to the view must implement ICommand interface. DelegateCommand wraps up all the members of the ICommand interface so that the app does not have to re-implement them every time. DelegateCommand.cs can be found in ServiceClasses folder.

_registerCommand as it is declared in constructor of the RegisterViewModel:

_registerCommand = new DelegateCommand(this.RegisterCommandAction, CanRegister); //initialize command
Method RegisterCommandAction is where the registration actually happens. This is what the command is supposed to do:
//Registration procedure
private void RegisterCommandAction(object obj)
{
    people.Add(new Person
    {
        Name = this.UserName,
        Gender = this.IsALady ? "Lady" : "Gentleman"
    });
    this.UserName = "";
    this.IsALady = false;
    this.IsAGentleman = false;
}
CanRegister method defines whether the command can execute or not. Here is what it looks like:
//Data consistency conditions that must be satisfied for a successful registration
private bool CanRegister(object arg)
{
    return (IsALady || IsAGentleman) && UserName.Length > 0;
}
This method enables/disables the register button in the view without having to bind the IsEnabled property of the button on the view. By the way, IsEnabled property can be managed by binding this property on the view to a boolean property on a view model. Pretty much any property on the view can be bound to a property on the view model, sometimes one might need to use a converter class (any class that implements an IValueConverter interface).

There is just one piece missing. The app somehow needs to know when CanRegsiter changes. For that, the DelegateCommand exposes a method called RaiseCanExecuteChanged(). Every time a property changes in the RegisterViewModel, not only the app has to notify that the property value changed, but it also has to make sure we can (or cannot) register. Here is, for example, an implementation of the UserName property in the view model:

private string _userName;
public string UserName
{
    get
    {
        if (_userName == null)
            _userName = "";
        return _userName;
    }
    set
    {
        if (_userName == value)
            return;
        _userName = value;
        this.OnPropertyChanged("UserName");
        this.OnPropertyChanged("ProspectiveRegistration");
        _registerCommand.RaiseCanExecuteChanged(); //checking on RegisterCommand to see if it can execute
    }
}

4. Testing

Now you can see that MVVM decouples View from ViewModel making it easy to test ViewModels. Visual Studio (even Express versions) provides tools for testing. A couple of test methods included with the Test project give a basic idea of how testing can go.

5. Conclusion

Hopefully this article explained the usage of MVVM pattern on Windows Phone. Again, the code is available on GitHub, feel free to ask questions and/or leave comments

UPDATE 3/16/2014:

I updated the code in the repository to include a technique where a Text propery (or another one for that matter) can be used as a CommandParameter. See the code for full example, but in short, here is what you need to do:

In the View, I added a TextBox, gave it an x:Name property of "typeSomethingHere" (that's the second named control on the entire page). Then I added a Button, and I assigned the following properties to it besides Content and position on the page:

Command="{Binding DisplayInputWithoutAdditionalPropertyCommand}"
CommandParameter="{Binding ElementName=typeSomethingHere, Path=Text}"
The Command property is just as usual, but the CommandParameter property shows the beauty and flexibility of XAML, it grabs the Text property from the TextBox.

The rest are details, I create a string property ShowWhatWasTyped and it displays what was typed.

What's the bottom line here? I could have bound the Text property of a typeSomethingHere TextBox and use it to get text, but instead I taught the button to grab Text directly from the TextBox and pass it to the ViewModel. This works well only if you need to pass one value into a method quickly. You can, of course, use other properties of the ViewModel in the method.


No comments: