MVVM Light on Windows Phone 7 - MVVM for the masses
Join the DZone community and get the full member experience.
Join For FreeSeparation of concerns is one of the concepts that sometimes might seem hard to understand while following older paradigms. Nonetheless, it is important to understand that MVVM brings several benefits to the table compared to other methodologies.
In simple terms, with MVVM there are separate entities in the context of the same application that are responsible for different tasks. In this case, the Model-View-ViewModel idea revolves around three entities (obvious from the name itself):
- Model - defines how data is kept internally, setting its structure
- View - defines how data is displayed
- ViewModel - the unit that facilitates the communication between the Model and the View
For those familiar with the MVC pattern, the ViewModel somewhat resembles the controller although it doesn't have direct solid ties to views.
To better illustrate the concept, let's create a sample MVVM Light application for Windows Phone 7. I will try to dissect the basic application to show you how the fundamental concepts are applied - consider it an extremely quick introduction to the MVVM pattern.
First of all, you need to download the MVVM Light framework from the CodePlex page. Laurent Bugnion, the creator of the framework, shows how to install the framework here.
I would highly recommend starting with this page for those who want to learn MVVM Light in depth.
Once you have the framework set up, you should see that there is a new project template available in Visual Studio.
The default project isn't much different from any other Windows Phone 7 project, but the MVVM concept is extremely simplified compared to the default templates bundled with Visual Studio.
By default, you have a ViewModels folder that contains two files - MainViewModel and ViewModelLocator:
And although the basic set doesn't offer much functionality, it demonstrates the MVVM idea quite well. First of all, take a look at the main page XAML. What's different compared to a regular page is the presence of a defined DataContext - the binding gateway. DataContext basically tells the page where the data for its components is obtained from.
DataContext="{Binding Main, Source={StaticResource Locator}}"
Main is the name of the bound ViewModel - the page has no idea what data will be passed and also it doesn't exactly know how the data is displayed. All it needs is a location where the data is obtained from, and the bound ViewModel (as mentioned above) is the link here.
However, Main is referenced in Locator - a ViewModelLocator instance, that actually manages ViewModel instances in MVVM Light. The actual resource that references the ViewModelLocator is located in the App.xaml file. A peek inside it reveals this:
<Application.Resources>
<vm:ViewModelLocator x:Key="Locator"
d:IsDataSource="True" />
</Application.Resources>
ViewModelLocator is the ViewModel manager in the application - you can have multiple ViewModelLocators implementations to handle different sets of ViewModels in a single application. Or you can also have different ViewModel instances set up in the context of the same ViewModelLocator.
Let's take a look at what the default structure of the ViewModelLocator looks like:
namespace TestMVVM.ViewModel
{
public class ViewModelLocator
{
private static MainViewModel _main;
public ViewModelLocator()
{
CreateMain();
}
public static MainViewModel MainStatic
{
get
{
if (_main == null)
{
CreateMain();
}
return _main;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public MainViewModel Main
{
get
{
return MainStatic;
}
}
public static void ClearMain()
{
_main.Cleanup();
_main = null;
}
public static void CreateMain()
{
if (_main == null)
{
_main = new MainViewModel();
}
}
public static void Cleanup()
{
ClearMain();
}
}
}
As you can see, there is no data included whatsoever, and this is to be expected - a ViewModelLocator has no idea what data goes through it. All it knows is that there is a specific ViewModel type that is managed by it. Its duty is to make sure that the ViewModel instance is accessible from the application. The current version of ViewModelLocator has one ViewModel defined - Main (instance of MainViewModel) - it can be created and destroyed internally
On the other side, the MainViewModel simply exposes a set of binding endpoints that can pass models to specific UI elements (Views):
using GalaSoft.MvvmLight;
namespace TestMVVM.ViewModel
{
public class MainViewModel : ViewModelBase
{
public string ApplicationTitle
{
get
{
return "MVVM LIGHT";
}
}
public string PageName
{
get
{
return "My page:";
}
}
public string Welcome
{
get
{
return "Welcome to MVVM Light";
}
}
public MainViewModel()
{
if (IsInDesignMode)
{
// Code runs in Blend --> create design time data.
}
else
{
// Code runs "for real"
}
}
}
}
If you look back the MainPage XAML, you will actually notice that the TextBox controls are bound to the properties defined in MainViewModel - each of them is tied to the ViewModel referenced by the DataContext in the page properties.
Nothing special here. To show you the actual benefit of having concerns separated, let's create a comptely different ViewModel - name it TestViewModel.
Make a complete duplicate of the existing ViewModel - it should have absolutely the same properties as MainViewModel, although with different values:
using GalaSoft.MvvmLight;
namespace TestMVVM.ViewModel
{
public class TestViewModel : ViewModelBase
{
public string ApplicationTitle
{
get
{
return "TestViewModel Here";
}
}
public string PageName
{
get
{
return "My page name";
}
}
public string Welcome
{
get
{
return "Really, welcome!";
}
}
public TestViewModel()
{
}
}
}
Time to modify the default ViewModelLocator to adapt it to a new ViewModel:
namespace TestMVVM.ViewModel
{
public class ViewModelLocator
{
private static MainViewModel _main;
public ViewModelLocator()
{
CreateMain();
CreateSecondary();
}
public static MainViewModel MainStatic
{
get
{
if (_main == null)
{
CreateMain();
}
return _main;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public MainViewModel Main
{
get
{
return MainStatic;
}
}
public static void ClearMain()
{
_main.Cleanup();
_main = null;
}
public static void CreateMain()
{
if (_main == null)
{
_main = new MainViewModel();
}
}
public static void Cleanup()
{
ClearMain();
ClearSecondary();
}
private static TestViewModel _mvvm;
public static TestViewModel MainTest
{
get
{
if (_mvvm == null)
{
CreateSecondary();
}
return _mvvm;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public TestViewModel Secondary
{
get
{
return MainTest;
}
}
public static void ClearSecondary()
{
_mvvm.Cleanup();
_mvvm = null;
}
public static void CreateSecondary()
{
if (_mvvm == null)
{
_mvvm = new TestViewModel();
}
}
}
}
Given that both models serve the same purpose it makes sense to integrate them in one locator for easy switching.
If it would be the case of different views and radically different functionalities, different locators are in order - having separate ViewModels managed by different locators also makes testing a bit easier, but that's another topic.
All you need to do now is edit the DataContext binding to reflect the new ViewModel:
DataContext="{Binding Secondary, Source={StaticResource Locator}}"
Launch the application and take a look at the result:
The simplicity with which you were able to switch models applies to testing and integration as well.
With an implemented MVVM layer it is very easy to work on separate parts of an application and not worry about damaging others because there is no one-to-one connection between the data and the UI. That being said, if I would have to modify a model to add complementary properties, I won't be afraid to break the internal structure because of solid ties to the controls used on the page.
Opinions expressed by DZone contributors are their own.
Comments