Geeks With Blogs

News
Joe Mayo Devices and Things

In my previous post, Using LINQ to Twitter in Windows 8 Metro Apps, I cheated a little (some might say a lot) on my UI architecture by using code-behind. In this post, I’ll make it all better by showing you how to separate the model and interaction logic from the UI design – proper separation of concerns.

I’ll accomplish this with a pattern, named Model-View-ViewModel (MVVM), which is widely used for WPF, Silverlight, and (soon) Windows 8 Metro Apps.  I’ll start by discussing what MVVM is, move to how I’ll apply it to the previous Windows 8 Metro app, and finish with a step-by-step walkthrough to refactor from code-behind to MVVM.

Understanding MVVM

The subject of whether to use MVVM or not can be debated forever.  However, it’s a useful way to achieve separation of concerns in your application and many people like and use it, including myself.

Jeremy Likeness has a nice site that specializes in the subject: MVVM Explained.

Designing Our Implementation

There are a handful of good frameworks for helping you write applications using MVVM. In this app, Since Windows 8 is so new, these frameworks aren’t generally available or ready. However, MVVM is a pattern, not a library, and you can always provide your own code to support the pattern, as you’ll see in this post.

This application will work exactly the same as the original, displaying public tweets when a button is clicked, but the internal implementation will be modified.  In particular, the classes and relationship between classes are altered to match the MVVM pattern. The following illustration shows this relationship:

MVVM

 

 

 

 

 

 

 

 

 

 

 

The three objects in the illustration above represent files for implementing a View, ViewModel, and Model. The view is MainPage.xaml, where you can see tweets and click the Refresh button. The model is an object that holds information for a single tweet. Actually, we’ll be working with a list of tweets, which will be held in the ViewModel and bound to the View.

In some diagrams, you’ll see a binding layer between the ViewModel and View, which is an important part of their relationship. In fact the binding relationship can be involved, but this application is so simple that I’ve also opted for a simpler diagram. Besides managing the model and supporting data binding, the ViewModel is instrumental in holding the current state of the view.  i.e. if you select a ComboBox option, it’s the View that holds onto the value that is selected. Here’s a breakdown in responsibilities for this app:

  • Model: Hold data for a single tweet. Technically, the list of tweets, held by the ViewModel is also a model component – instead of a single instance it’s a collection of instances.
  • ViewModel: Hold a list of current tweets and provide a command handler that responds to Refresh button clicks and populates the list of tweets.
  • View: Display tweets and let the user click a refresh button.

Refactoring the Model

In the previous post, I named the object bound to the View with a ViewModel suffix, TweetViewModel. To fix this, select the TweetViewModel.cs file, in the Solution Explorer Project, press F2 and change the name to Tweet.cs. You’ll see a message box asking if you want to do a rename and you should click the Yes button, which renames the class name in the file as well as any declarations of that class in the project.

Next, rename the Tweet property, of the Tweet class by selecting the property name, Tweet, press Ctrl+R+R, change Tweet to Text in the New name box of the Rename message box that appears, and click OK. Here’s the updated Tweet class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RefactorToMVVM
{
    public class Tweet
    {
        public string Name { get; set; }
        public string Text { get; set; }
        public string ImageUrl { get; set; }
    }
}

Note: I’ve also done a refactoring of the namespace, LinqToTwitterPublicQuery, to RefactorToMVVM.

Though this class was previously called a ViewModel, the name didn’t capture the essence of what a ViewModel is about: model access, binding, and state management. You’ll see some of this in the next section.

Creating the ViewModel

The ViewModel is often described as “A model of the View”, which is a non-visual abstraction of the view. The ViewModel supports separation of concerns where the concern of designing a UI occurs in the XAML, but the concern of state management and responding to events belongs to the ViewModel. In this simple ViewModel, we’ll add just enough functionality to be architecturally correct, but no more than is needed for this application. More specifically, we’ll create the PublicTweetsViewModel, shown below, that references a model and responds to refresh commands from the View (remember to add a project reference to LinqToTwitter.dll):

using System.Collections.Generic;
using System.Linq;
using LinqToTwitter;
using Windows.UI.Xaml.Data;

namespace RefactorToMVVM
{
    public class PublicTweetViewModel : INotifyPropertyChanged
    {
        public List<Tweet> Tweets { get; set; }

        public PublicTweetViewModel()
        {
            RefreshCommand = new TwitterCommand<object>(OnRefresh);
        }

        public TwitterCommand<object> RefreshCommand { get; set; }

        void OnRefresh(object obj)
        {
            var twitterCtx = new TwitterContext();

            Tweets =
                (from tweet in twitterCtx.Status
                 where tweet.Type == StatusType.Public
                 select new Tweet
                 {
                     Name = tweet.User.Name,
                     Text = tweet.Text,
                     ImageUrl = tweet.User.ProfileImageUrl
                 })
                .ToList();

            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Tweets"));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

The code above has a constructor, Tweets collection property, RefreshCommand property, OnRefresh method, and PropertyChanged event. The Tweets collection will hold a list of public tweets and is populated by the OnRefresh method. Tracing back, the constructor adds the OnRefresh method as a handler for the RefreshCommand property, using the TwitterCommand, which is a special implementation of ICommand. Here’s the implementation of TwitterCommand, which you should add to another file, named TwitterCommand.cs:

using System;
using Windows.UI.Xaml.Input;

namespace RefactorToMVVM
{
    public class TwitterCommand<T> : ICommand
    {
        readonly Action<T> callback;

        public TwitterCommand(Action<T> handler)
        {
            callback = handler;
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event Windows.UI.Xaml.EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            if (callback != null) callback((T)parameter);
        }
    }
}

The TwitterCommand constructor accepts an Action delegate, assigning handler to callback. When the View triggers the RefreshCommand in PublicTweetViewModel, Execute is invoked, which then invokes the callback delegate that the PublicTweetViewModel constructor assigned to the OnRefresh method.

When OnRefresh executes, it isn’t enough to just assign results to Tweets, you must also let the View know it should display the new data. This is where the INotifyPropertyChanged interface helps. The PropertyChanged event is a member of INotifyPropertyChanged and the View listens to this event. The following code is a snippet of PublicTweeViewModel, shown previously, with only the INotifyPropertyChanged related code:

...
    public class PublicTweetViewModel : INotifyPropertyChanged
    {
        ...
        void OnRefresh(object obj)
        {
            ...
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Tweets"));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

After LINQ to Twitter gets new tweets, OnRefresh fires the PropertyChanged event, passing the property name, Tweets, that changed. This tells the View to re-bind to Tweets.

With the ViewModel in place, all that’s left to do is hook up the ViewModel in the View and complete binding so the View doesn’t use code-behind anymore.

Binding the ViewModel to the View

The first View related task is to fix the code-behind problem. Essentially, remove all unnecessary code from MainPage.xaml.cs. So, you can delete the entire RefreshButton_Click method and set the DataContext for the View, like this:

namespace RefactorToMVVM
{
    partial class MainPage
    {
        public MainPage()
        {
            InitializeComponent();
            DataContext = new PublicTweetViewModel();
        }
    }
}

Notice how the MainPage constructor sets its DataContext property to an instance of PublicTweetViewModel. I won’t put any more code in the code-behind.

Note: I could have avoided putting any extra code in the code-behind by setting the DataContext in XAML. Unfortunately, the preview version of VS11 doesn’t support that yet.  If you do, you’ll see a message like “Method 'add_PropertyChanged' in type 'RefactorToMVVM.PublicTweetViewModel' from assembly 'refactortomvvm.intermediate, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.    C:\Users\jmayo\Documents\Visual Studio 11\Projects\RefactorToMVVM\RefactorToMVVM\MainPage.xaml    RefactorToMVVM”.

Now, all the binding expressions in MainPage.xaml can reference properties of PublicTweetViewModel. This includes binding RefreshButton and PublicTweetListBox, like this:

<UserControl x:Class="RefactorToMVVM.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="768" d:DesignWidth="1366">
  
    <Grid x:Name="LayoutRoot" Background="#FF0C0C0C">
        <Button Content="Refresh Public Tweets" Height="72" Width="365" 
                HorizontalAlignment="Left"  Margin="500,66,0,0" 
                Name="RefreshButton" VerticalAlignment="Top"
                Command="{Binding RefreshCommand}"/>
        <ListBox Height="465" HorizontalAlignment="Left" Margin="5,144,0,0" 
                 Name="PublicTweetListBox" VerticalAlignment="Top" Width="1355"
                 ItemsSource="{Binding Tweets}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" Height="132">
                        <Image Source="{Binding ImageUrl}" 
                               Height="73" Width="73" 
                               VerticalAlignment="Top" Margin="0,10,8,0"/>
                        <StackPanel Width="370">
                            <TextBlock Text="{Binding Name}" 
                                       Foreground="#FFC8AB14" FontSize="28" />
                            <TextBlock Text="{Binding Text}" 
                                       TextWrapping="Wrap" FontSize="24" />
                        </StackPanel>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>   
</UserControl>

For RefreshButton, remove the Click event handler and replace it with the command binding shown above. This is how the RefreshCommand property in the PublicTweetViewModel, which is an instance of TwitterCommand binds to the button.

Since we already deleted the RefreshButton_Click method from the code behind, we need to tell PublicTweetListBox to get it’s data by binding the Tweets collection, from PublicTweetViewModel, to the ItemsSource property.

Within the DataTemplate for PublicTweetsListBox, each individual tweet binds the same as the previous post. The exception is that you must change the TextBlock binding for the previously named property, Tweet, to Text. If you recall, we changed the Tweet property of the model (also named Tweet) to Text.

That’s it, press F5 to run the application. You’ll notice that it works exactly like the previous application where pressing the Refresh Public Tweets button will load public tweets to the screen.

Summary

In this post, I attempted to redeem myself from the supposed crimes of those who would be offended. Seriously, the MVVM pattern is pretty slick in how it relies primarily on binding and keeps the UI design separate from the implementation. You saw an image of how the previous code would be refactored to support MVVM. Then I took you through a series of steps that refactored the Model, created a new ViewModel and an ICommand implementation. Finally, you learned how to connect the ViewModel to the View and bind the ICommand to the Button and data to a ListBox. The result is an application that works the same as its code-behind predecessor, yet is more maintainable because of the clean separation of concerns that MVVM helps with.

@JoeMayo

Posted on Monday, January 30, 2012 9:22 AM Twitter , LINQ to Twitter , Windows 8 | Back to top


Comments on this post: Refactoring Windows 8 Code-Behind to MVVM

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © Joe Mayo | Powered by: GeeksWithBlogs.net | Join free