Introduction
This article details a real world example of using a Model-View-ViewModel Design Pattern. I split the development process into steps. Each step is based on the previous one and adds some specific functionality. At the end of each step, we will have a working application.
As an example I chose Twitter Client. I hope that this application is comprehensible and useful. I’ll use TweetSharp library to make work with twitter API simple and not cumbersome.
The reader is supposed to have some experience of work with WPF, in particular, having investigated data binding.
I will not write here why you should use MVVM while working with WPF. Yet if you want to read in details about the motivation for using this design pattern, I would recommend you to read “WPF Apps with the Model-View-ViewModel Design Pattern” by Josh Smith. On my part, I would like to say that the design which smells is a big pain, especially if you use WPF.
You also may download a bit more developed twitter client on github: https://github.com/KirillLykov/AnhingaTwitterClient
Background
A Twitter Client shows tweets grouped according to some criterias. It also allows submitting tweets and sends direct messages. Finally, it provides friends management – a user can “follow” somebody what means he or she will see the followings tweets in his or her tweetlist.
We start off with looking at an existing Twitter Client before writing code. This analysis will clarify how our client should look and what controls we should use.
There are several bookmarks at the top of the window - recent, replies, user, etc. Depending on the chosen bookmark, the different information is displayed in the main part of the window. For instance, if the recent bookmark has been chosen, there will be the list of the last tweets. If the messages bookmark has been chosen, there are direct messages.
This short analysis leads to the solution to use TabControl
. It will display the pages with various contents and show the name of the chosen TabItem
at the Top.
Brief Introduction to MVVM
First, I want to describe the situation when a developer tries to create an application using WPF but without using MVVM. It’ll provide the starting point and demonstrate benefits of using MVVM in a real- life example.
Imagine you need to write a UserControl
that displays tweets. Imagine also that you decided not to learn MVVM and prefer to do everything the same way, you would do it if you used a framework of the previous generation, like WinForms. Perhaps, you will press “Add UserControl” in VS, and then add controls you need in XAML. After that, you will add to the code behind a property which will store tweets. Finally, you will add to the code behind methods which will handle events like button mouse click, etc. At the end, you will have some XAML code which describes graphical design and code behind which stores data and contains events handlers.
Let us have the first feature request– users need to see some other tweets, not those you store in your property. But they are ok with the design of your control. Perhaps, you will sort it out by adding a flag in your code behind. If it is true, than you show those tweets that you showed them one group tweet, otherwise another one. Not a great solution, but it works.
Time passed and you’ve got the second feature request – some users think that every tweet contains too much redundant information. Thus, you should create a new look of tweets. At the same time, you don’t want to offend those users who like the old look… You definitely will create some sophisticated solution for this problem but it is not the last feature request you will get. And the more you get, the more difficult it is to solve them…
The question is whether it is possible to design the application in the way which allows avoiding copy-paste and simplifying the code. What if we move the code behind user control to a separate class? In this case, we will have one class inherited from UserControl
which represents the user interface. Call it View. And another class represents data directly connected with View, e.g., those data and event handlers which might be in code behind. Call it ViewModel.
This differentiation into View and ViewModel would easily solve the first feature request. We needed to display the new group of tweets in the same way as the first one. All the developer would need is to create a new ViewModel
class which contains the new data and map it into the View he or she already have.
The second feature request could be solved by writing a new View and using the old ViewModel.
The only question is how to create this mapping between View and ViewModel. The data binding is the tool which will provide this mapping. Imagine we have a View and a ViewModel which know nothing about one another. In the third class (I will call it Main View), where we are going to use View, we will set up the right DataContext
for our View. It will not cause any changes in the code of View or ViewModel. I deliberately skip the technical details. I will describe them in the following paragraphs.
If you like the UML class diagram, here we have one describing the typical application written with MVVM:
We see View classes which use data from ViewModel
classes. It is essential that View classes know nothing about Model. The Model encapsulates all business logic and provides data for ViewModel
. ViewModel
organizes data the way suitable for View. ViewModel
knows nothing about Views use it.
We also have a special View
and ViewModel
classes I called “Main
”. The Main View is not a widespread term, but I introduced it in order to simplify the description of the MVVM. It is the main UserControl
which displays all others. It sets up data context for the View
classes it uses. The Main ViewModel
contains all ViewModel
s for particular UserControl
s.
Step 1. The Skeleton of the Application
Start with creating the application with TabControl
containing only one tabItem
. Call it “Recent”.
Create a new WPF application and add TabControl
in the MainWindow
:
<TabControl Height="400"
HorizontalAlignment="Left"
Name="Tabs"
VerticalAlignment="Top" Width="300">
<TabItem Header="Recent" Name="RecentTab"/>
</TabControl>
The MainWindow
is the Main View in our application. It will contain all other Views which will be represented in the TabItem
s. It must have an appropriate Main ViewModel
. Call it SimpleTwitterClientViewModel
. It will contain necessary data and methods which handle the events like mouse button click. Point out that the Main View must get data from the Main ViewModel
(by default, it gets them from the code behind). What we should do is to set up the DataContext
:
<Window x:Class="SimpleTwitterClient.MainWindow"
x:Name="MainWindowInstance"
xmlns:view="clr-namespace:SimpleTwitterClient.View"
xmlns:viewModel="clr-namespace:SimpleTwitterClient.ViewModel"
DataContext="{BindingViewModel,ElementName=MainWindowInstance}">
public partial class MainWindow : Window
{
private SimpleTwitterClientViewModel _viewModel;
public SimpleTwitterClientViewModel ViewModel
{
get { return _viewModel; }
set { _viewModel = value; }
}
public MainWindow()
{
_viewModel = new SimpleTwitterClientViewModel();
InitializeComponent();
}
}
Create a new UserControl
, call it RecentView
, and add a button to it. Create also a new class RecentViewModel
.
SimpleTwitterClientViewModel
is to hold ViewModel
s for all TabItem
s in the TabControl
. Right now, we have only “Recent
” so write the following code:
public class SimpleTwitterClientViewModel
{
RecentViewModel _recentPage = new RecentViewModel();
public RecentViewModel RecentPage
{
get { return _recentPage; }
set { _recentPage = value; }
}
}
Get our TabItem
to display RecentView
. First, set up the Content
of the TabItem
into the RecentPage
:
<TabItem Header="Recent" Name="RecentTab" Content="{Binding Path=RecentPage}"/>
Second, create a mapping between RecentView
and RecentViewModel
:
<Window.Resources>
<DataTemplateDataType="{x:TypeviewModel:RecentViewModel}">
<view:RecentView />
</DataTemplate>
</Window.Resources>
After all these manipulations, we will have the application like the following:
At this point, the applying of MVVM may seem a bit cumbersome but at the following steps, you will appreciate the merits of MVVM.
OAuth Authorization Process
The Twitter
API uses the OAuth 1.0 protocol. In order to use an application with the Twitter API, we have to write some code which will perform the necessary authorization steps. If you are not interested in the detailed description of this code, you skip the text below but get the class called OAuthHandler
from the enclosed code and use it. it is a simple singleton which does all the authorization work.
- ConsumerKey and ConsumerSecret. Go to the site http://dev.twitter.com/ and click register. Register as the developer there and then register your application. After that, you will see the page with information about your application. Get there
ConsumerKey
and ConsumerSecret
and save them in the application’s settings.
- PIN (oauth_verifier). When your application will be launched for the first time, it must send a request with the
ConsumerSecret
and ConsumerKey
to the server:
FluentTwitter.SetClientInfo(
new TwitterClientInfo
{
ConsumerKey = Settings.Default.ConsumerKey,
ConsumerSecret = Settings.Default.ConsumerSecret
});
var twit = FluentTwitter.CreateRequest().Authentication.GetRequestToken();
var response = twit.Request();
var RequestToken = response.AsToken();
twit = twit.Authentication.AuthorizeDesktop(RequestToken.Token);
The last line of this code will open the default browser. After clicking “Allow” at the appeared page, you will see the PIN. You need it in your application so create a dialog which asks the PIN from the user. I call this dialog in the method getPinFromUser
:
string verifier = getPinFromUser();
- AccessToken. Now, send the request containing
consumerKey
, consumerSecret
, and Pin
. The service will return AccessToken
which will be used to work with application further, so save it somewhere:
twit.Authentication.GetAccessToken(RequestToken.Token, verifier);
var response2 = twit.Request();
- Refreshing your
AccessToken
. After time passed, you need to get the new AccessToken
.
Step 2. Displaying tweets
Fill in the RecentPage
by the real data, i.e. the last tweets posted by the user’s followings and the user’s own tweets. In order to do that, we will use TweetSharp
library. Add it to the list of references for your project.
Before starting, add the classes OAuthHandler
and AskPinFromUserDialog
from enclosed code to the Model folder. Add ConsumerKey
, ConsumerSecret
, OauthInfoFileName
to settings. OauthInfoFileName
contains the path to the config file where we store AccessToken
. Perhaps, it is not the greatest solution but it is simple and works. Finally, add the creation of the OAuthHandler
to the Main ViewModel
:
Model.OAuthHandler _oauthHandler = newModel.OAuthHandler();
Now, we can start working with RecentPage
. First, add the container which will hold tweets to the RecentViewModel
:
public ObservableCollection<TwitterStatus> Tweets
{
get; set;
}
We used ObservableCollection
instead of regular .NET container like List
because ObservableCollection
synchronizes the data with the View automatically, i.e., we needn’t wait for event “data changed” in order to refresh View.
Now, download tweets. First, create a constructor for the RecentViewModel
which gets the OAuthHandler
as the parameter and save it into the _oauthHandler
field. After that, add the LoadTweets
method:
public void LoadTweets()
{
TwitterResult response = FluentTwitter
.CreateRequest()
.AuthenticateWith(
Settings.Default.ConsumerKey,
Settings.Default.ConsumerSecret,
Model.OAuthHandler.Token,
Model.OAuthHandler.TokenSecret)
.Statuses()
.OnHomeTimeline().AsJson().Request());
var statuses = response.AsStatuses();
foreach (TwitterStatus status in statuses)
{
Tweets.Add(status);
}
}
For the first time, call LoadTweet
right in the constructor of RecentViewModel
:
public RecentViewModel(Model.OAuthHandleroauthHandler)
{
_oauthHandler = oauthHandler;
Tweets = newObservableCollection<TwitterStatus>();
LoadTweets();
}
Then create ListBox
which will display tweets:
<ListBoxx:Name="RecentTweetList"
ItemsSource="{Binding Path=Tweets}"
IsSynchronizedWithCurrentItem="True"/>
If you execute the application now, you will see the list with the result of invoking method TweetStatus.ToString()
. It is because the elements of the ListBox
don’t know how to display the objects of type TweetSharp
. In order to do that, we need to add a DataTemplate
to the UserControl.Resources.DataTemplate
should contain the XAML code which determines the look of tweet. It should contain textbox
for the text of the tweet, image for the userpic and so on. For every element like this one, we need to write binding to the appropriate property of the TweetSharp
type object:
<DataTemplate x:Key="TweetItemTemplate">
<Grid x:Name="TTGrid">
<Grid.RowDefinitions>
<RowDefinition Height="5*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5*" />
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="17*" />
</Grid.ColumnDefinitions>
<Image Source="{Binding Path=User.ProfileImageUrl}"
Name="UserAvatarImage" />
<TextBlock
Name="ScreenNameTextBlock"
Text="{Binding Path=User.ScreenName}"
Grid.Row="1"Grid.ColumnSpan="2"/>
<TextBlock
Text="{Binding Path=Text}"
TextWrapping="Wrap"Grid.Column="1"Grid.ColumnSpan="2" />
</Grid>
</DataTemplate>
Finally, point that the ListBox
must use this DataTemplate
:
ItemTemplate="{StaticResourceTweetItemTemplate}"
Launch the application. It should look like that:
Step 3. More Pages
In this paragraph, we create an additional page which contains retweets.
The recent bookmark displays the last tweets being written by user’s followings or by the user him / her self. The method which downloads this group of tweet called HomeTimeline
in TweetSharp
. Yet it is not the only group of tweets that might be desirable for the user and in the class TwitterStatusesExtensions
you may find methods that download other groups of tweets: OnFriendsTimeline
, OnListTimeline
, RetweetedByMe
…
For instance, we’ll choose all retweets posted by your friends. In order to do that, we should create class RetweetsViewModel
which is to store all tweets obtained by RetweetedToMe()
method. I’ll not write this code here, let it be a simple exercise. Note you don’t need to code a RetweetsView
class. I will explain why at the end of the paragraph.
What we need to do now is to add the RetweetsViewModel
object to the main ViewModel
. Actually, we can do that by adding the new property for it and in the case of two pages, it’s ok. Yet imagine that we want to deal with many pages in TabControl. In this case, it makes sense to store pages in some container.
The RecentViewModel
is very similar to the RetweetsViewModel
. We can extract an interface IPageBase
from the RecentViewModel
and make the RetweetsViewModel
class implement it. We also may make both of them have default constructors by a property:
public interface IPageBase
{
void LoadTweets();
ObservableCollection<TwitterStatus> Tweets { get; set; }
}
Now, the time to change the code of the main ViewModel
comes:
ObservableCollection<IPageBase> _pages;
public ObservableCollection<IPageBase> Pages
{
get { return _pages; }
set { _pages = value; }
}
public SimpleTwitterClientViewModel()
{
_pages = new ObservableCollection<IPageBase>();
_pages.Add(new RecentViewModel());
_pages.Add(new RetweetsViewModel());
foreach (var page in _pages)
{
page.LoadTweets();
}
}
Finally, we shall add the new DataTemplate
for the RetweetsPage
in the Main View. Ok, there we get a problem. Before we have had only one TabItem
which was bound to the property RecentPage
in the Main ViewModel
:
<TabItem Header="Recent" Name="RecentTab" Content="{Binding Path=RecentPage}" />
Now we can’t be satisfied with such a solution because we need to have as many TabItem
s as the amount of pages there are. Fortunately, there is a smart property ItemsSource
in the TabControl
class so we can just bind your container to it.
Yet there is an issue. A TabItem
has a Header
which displays the name of the page but if our TabItem
s has been automatically generated by the ObservableCollection
, it is not clear how to set TabItem
s headers. Don’t panic, first, add Name
property to the IPageBase
interface and implement it in every page class. Then create a style for the TabItem
’s Header
:
<TabControlName="Tabs"
ItemsSource="{Binding Pages}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
At the end, we will add View
for our RetweetsViewModel
. Of course, you may create a RetweetsView UserControl
by copy-pasting code from the FriendsView
. But there is a better solution. You may use RecentView
as is:
<DataTemplateDataType="{x:TypeviewModel:RetweetsViewModel}">
<view:RecentView />
</DataTemplate>
Finally, rename RecentView
to TweetsView
. That’s all. Now, our application should look like that:
If you don’t like writing two similar DataTemplates
, you might create a superclass for these ViewModels
and specify only one DataTemplate
for this superclass.
As an exercise, you may implement View
and ViewModel
for the list of user’s followings and followers.
Step 4. Sending tweets and ICommand
Once previous 3 steps are completed, a user gets an application which allows reading tweets. But user still can’t send tweets. In this paragraph, I will describe how to implement this feature according to MVVM.
Add TextBox
and Button
at the bottom of the Main View:
The main question of this paragraph is how to handle events raised by control elements in the View. If you liked to have Send button click event handler in the code behind, you would click on this button right in the designer and would write code in the created function in the code behind. So far as we use MVVM, the method that handles the event must be handled in the Main ViewModel
. It leads to a bit more tricky code.
First, add the Message
property to the Main ViewModel
. Then bind your tweet textbox to Message
property:
<TextBoxName="EnterTweetTextBox"
Text="{BindingMessage}"/>
Write a method which will send tweet:
private void SendTweet()
{
var twitter = FluentTwitter.CreateRequest();
twitter.AuthenticateWith(
Settings.Default.ConsumerKey,
Settings.Default.ConsumerSecret,
OAuthHandler.Token,
OAuthHandler.TokenSecret);
twitter.Statuses().Update(Message);
var response = twitter.Request();
}
There is only one problem to solve – how to specify that SendTweet
method should be called when the Send button is clicked. The Button
class has a relevant property Command
. It specifies the method which invokes when the button is pressed. Great, but Command
has a type ICommand
, while your SendMessage
, of course, is just a function and it can’t implement this interface. The convenient solution is to use Adapter pattern from the “Design Patterns” book. It is easy but we don’t need to write this adapter because it has been already written by Josh Smith.
internal class RelayCommand : ICommand
{
#region Fields
readonly Action _execute;
readonly Func<bool> _canExecute;
#endregion
#region Constructors
public RelayCommand(Action execute)
: this(execute, null)
{
}
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute();
}
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_canExecute != null)
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
_execute();
}
#endregion }
Class RelayCommand
just adapts delegate to the interface ICommand
. To use it in the code, you need to create a property which wraps the SendTweet
with ICommand
:
RelayCommand _sendCommand;
public ICommand SendCommand
{
get
{
if (_sendCommand == null)
{
_sendCommand = new RelayCommand(() => this.SendTweet());
}
return _sendCommand;
}
}
Finally, let we make SendButton
to use SendCommand
as it’s handler:
<Button Name="SendTweetButton"
Command="{Binding SendCommand}"/>
Not really tricky, isn’t it? That’s how the tweeter client looks like now:
In order to make it look like the existing twitter clients, I added a stylistic effect. The textbox
displays how many symbols user may enter.
As an exercise, you may implement the following feature. If user clicks on the userpic, the TabItem
will be changed. It will display only tweets posted by this following.