Catel is a brand new framework (or enterprise library, just use whatever you like) with data handling, diagnostics, logging, WPF controls, and an MVVM Framework. So, Catel is more than "just" another MVVM Framework or some nice Extension Methods that can be used. It's more like a library that you want to include in all the (WPF) applications you are going to develop in the near future.
This article explains using Catel as an MVVM framework on Windows Phone 7.
Article browser
Table of Contents
- Introduction
- Creating the project
- Understanding the project
- Adding Bing maps
- Setting up the view model
- Conclusion
1. Introduction
Welcome to part 6 of the articles series about Catel. If you haven’t read the previous article(s) of Catel yet, it is recommended that you do. They are numbered so finding them shouldn’t be too hard.
This is the first article about using Catel as an MVVM framework on Windows Phone 7. The software being written in this article is very simple and does not include any navigation. The software used for this article is based on an article by Joost van Schaik in the Dutch version of the .NET magazine. Below is the end result of the application we will create during this article:
Since version 1.4, Catel fully supports Windows Phone 7. This article is meant as a simple introduction to the Windows Phone 7 implementation of Catel. In a later stadium, more articles will be written about Catel and Windows Phone 7. If you have a good idea for an article, just let us know!
1.1. Prerequisites
Catel is getting more and more complete. One of the latest features added is a Visual Studio 2010 extension that installs the project and item templates for Catel. This article assumes that you have installed this extension for Catel. If you haven’t, please read the documentation on how to install the extension.
This article will also use NuGet, the new way to get the latest version of Open-Source projects. Catel is available via NuGet, and this article will explain how to get the latest version of Catel and add it as a reference. It is also possible to download and reference the assemblies of Catel manually if you prefer, but that is outside the scope of this article.
Another assumption is that you have installed the Windows Phone 7 Developer Tools, which is required to develop applications for Windows Phone 7.
2. Creating the project
Good software development is about ordering your code and making sure that everyone in your development team can understand your code. Everyone reading this of course has a black belt in software development, but there are also people that are less gifted that need to understand your genius code. Therefore it is very wise to order your code consistently, and that’s where this article will start with as well.
This article uses the directory C:\Source\Articles as a base directory, but you can choose any directory you want. Inside this directory, create a new directory named BingMapsDemo. This will be the directory where we put all our files in (not only our source files), and will be called [basedir] from now on. Inside [basedir], create the following directories:
- doc (put all your documents in here)
- lib (put all your libraries in here)
- src (put all your source in here)
Assuming that the templates are installed to Visual Studio, let’s create a new project for Windows Phone 7. When creating a new project, the Silverlight for Windows Phone node contains a subnode named Catel. Select the Catel node and create a new Windows Phone Application with Catel.
In the solution of Catel, this example application is named Catel.Examples.WP7.BingMaps, therefore it is wise to name the project the same. You have the freedom to give any name to the project, but the namespaces in the code of this article assume Catel.Examples.WP7.BingMaps. Make sure to set the location to [basedir].
The project is now ready to be created. Now comes the bad part of being consistent and organizing your code. Visual Studio does not do it the way I (and lots of other developers as well) prefer. Therefore, we need to do a manual copy step. Close Visual Studio and copy everything from [basedir]\Catel.Examples.WP7.BingMaps to [basedir]\src. Finally delete the directory [basedir]\Catel.Examples.WP7.BingMaps. This will result in the following directory structure:
Disclaimer
You will now probably be thinking: what the ****. However, once you learn to organize all your software projects like this, all you will get is compliments from other people how organized, clean, and easy to understand your software projects are.
3. Understanding the project
We’ve just created the project using the Windows Phone 7 application template with Catel. In this part of the article, we are going to reference the latest Catel assemblies and make sure you fully understand what the project template has created for you.
Load the solution from [basedir]\src into Visual Studio. When you try to build the solution, it will fail. We will take care of that issue right now.
3.1. Referencing Catel via NuGet
Catel is not yet referenced by the project. This is the task of NuGet because that way, you can make sure you have got the latest version available. This article was written when Catel 1.4 was not yet released, so it uses a local repository. By the time this article is available, Catel 1.4 will be released and you should be able to grab the latest version via the on-line NuGet package source.
Right-click on the Catel.Examples.WP7.BingMaps project and choose Add Library Package Reference. Search for Catel, select the right package (Catel.WP7), and finally click Install. The NuGet package will now be added to your project.
If you try to build the solution now, it actually can compile. You just successfully created your first Windows Phone 7 application using Catel. For fun, just try to run it and you will see an empty page in the Windows Phone 7 emulator.
3.2. Understanding the project structure
It is very important that you understand how the project is structured. Let’s start with the solution directory itself. It now contains four directories (doc, lib, output, and src). The output directory contains all the output files, so you no longer have to search for your output files. Makes things much easier, don’t you think :).
Let’s take a look at the project itself. Go back to Visual Studio and take a look at the project. Below is an image of the project with all the folders expanded:
The first thing that is noticeable is that a new UI folder is created that contains the MainPage view and the MainPage ViewModel. The project also contains a Data folder where all data objects should be located in. When you take a look at the code-behind of MainPage.xaml.cs, you see that there are two classes. It looks a bit dumb, but there is a very good reason for that. Silverlight (and thus Windows Phone 7) do not allow classes to directly derive from generic classes. To bypass that issue, an intermediate class is created so Silverlight will no longer complain. The follow situation is a visual representation of the class hierarchy of MainPage
:
You can either worry about the additional class, or accept this behavior of Silverlight. If you care about your health, choose the latter. The intermediate pages are always generated for you so you never have to write them yourself.
Last but not least, let’s take a look at the MainPageViewModel
class which actually defines the logic of the view. You see there are two classes again:
MainPageViewModel
: The actual view model. It contains all the real logic of the view.
DesignMainPageViewModel
: The design time implementation of the view model. If you don’t need design time, simply remove this one.
For now, this is all there is to understand about the project. We’ll dive into extending the functionality in the next chapter.
4. Adding Bing maps
You are doing great so far, and it wasn’t that hard, was it? Adding Bing maps is quite some work, and since the actual working of Bing Maps is out of scope, I have decided to take a shortcut. I will explain all the steps that you will need to execute to add support, but I will not explain the inner workings of Bing Maps.
Again, all credits for the actual Bing Maps implementation go to Joost van Schaik. He wrote them initially in his .NET magazine article, but was kind enough to allow me to use it as an example in combination with Catel.
4.1. Adding references
To enable GPS and Bing Maps, add a reference to the following assemblies:
- System.Device
- Microsoft.Phone.Controls.Maps
4.2. Adding source files
Lots of source files are required to implement Bing Maps. We are cheating a bit here, but I don’t want you to have to copy all the source code yourself. Therefore, simply extract all the files from BingMapsCode.zip to [basedir]\src\Catel.Examples.WP7.BingMaps.
In Visual Studio,
- enable hidden files and
- include the items in the project like visually represented in the figure below:
4.3. Setting up the view
The MainPage
itself requires to display the Bing Maps control. Below is the source code of the content of the page. Replace the whole Grid
generated by the project template by the Grid
below:
<!---->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!---->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="MY APPLICATION"
Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="{Binding Title}"
Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!---->
<Grid Grid.Row="1" x:Name="ContentPanel">
<Grid.RowDefinitions>
<RowDefinition Height="0.13*" />
<RowDefinition Height="0.87*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.167*" />
<ColumnDefinition Width="0.667*" />
<ColumnDefinition Width="0.167*" />
</Grid.ColumnDefinitions>
<Maps:Map Grid.Row="1" Grid.ColumnSpan="3" x:Name="map"
CredentialsProvider="developer_api_here"
Center="{Binding MapCenter}"
ZoomLevel="{Binding ZoomLevel, Mode=TwoWay}"
LocalUI:BindingHelpers.TileSource="{Binding CurrentMap}">
<Maps:Map.Mode>
<MSPCMCore:MercatorMode />
</Maps:Map.Mode>
</Maps:Map>
<Button Content="<">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<MVVM:EventToCommand Command="{Binding PreviousMap}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Button Content=">" Grid.Column="2">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<MVVM:EventToCommand Command="{Binding NextMap}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<TextBlock Grid.Column="1" TextWrapping="Wrap"
Text="{Binding CurrentMap.Name}" Margin="0,23,0,0" HorizontalAlignment="Center"/>
</Grid>
</Grid>
It might look a bit overwhelming, but basically there are just a few controls on the page. In the center, there is the Map
control. Then there are two buttons (< and >) which allow the user to switch between different map types. The last piece of code is a TextBlock
showing the currently selected map so the user knows to what type of map he or she is looking at.
If you try to build the software now, it will fail because you need to set a few references in the declaration of the XAML page:
xmlns:Maps="clr-namespace:Microsoft.Phone.Controls.Maps;assembly=Microsoft.Phone.Controls.Maps"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:MVVM="clr-namespace:Catel.MVVM;assembly=Catel.WP7"
xmlns:MSPCMCore="clr-namespace:Microsoft.Phone.Controls.Maps.Core;
assembly=Microsoft.Phone.Controls.Maps"
xmlns:LocalUI="clr-namespace:Catel.Examples.WP7.BingMaps.UI"
You can now build and run your application. It will show the page as designed, but you are not yet able to view or switch between maps. However, you can now get a grip of how the final result will look like.
5. Setting up the view model
This chapter will focus on implementing the actual view logic in the view model. First we start by defining properties. Then, we will create methods that will add logic and behavior to the properties. After the methods, we will implement commands so the view can interact with the view model. Last but not least, we will do something cool that will become available in the next Mango update: simulate geographical locations. However, using Catel, you don’t have to wait until the autumn of 2011, but you can start using it immediately.
5.1. Cleaning up
This chapter actually starts with cleaning up the view model. The project template created default properties and behavior to match the default Microsoft templates as close as possible. However, we don’t need them in this project so we are starting with a cleanup.
The project template already created a property Items
which we are not going to use; remove it (don’t forget the RegisterProperty
line).
Next, delete the Initialize
method as well. It is added for backwards compatibility, but will be removed in version 2.0 of Catel. We are not going to use validation, nor will we save any data, thus the ValidateFields
, ValidateBusinessRules
, and Save
methods can be deleted as well.
This article does not describe the creation of design time view models for Bing Maps. Therefore the last thing to do is to delete the DesignMainPageViewModel
class.
The view model class looks much cleaner now, doesn’t it? Even if you don’t think so, we are heading for the next part of this chapter!
5.2. Properties
When using Bing Maps, there are a few properties that are important things that the Map
control needs to be aware of. Again, I want to mention the clarity of the code. I like to organize my code into regions. Some people seem to be scared for regions, they start screaming and running around. If you don’t like regions, simply throw them away. But for the sake of clarity during the development of the view model in this article, please keep them there for a while. All properties used in this article will be added to the Properties\View model region (yes, nested regions, love them :)).
5.2.1. AvailableMapSources
First of all, the fictional user wants to be able to view several different maps. There must be some kind of list that contains these maps.
Steps
- Use the vmprop code snippet
- Add description Gets or sets the available map sources, then hit tab
- Add type
ObservableCollection<BaseTileSource>
, then hit tab
- Finally use the name
AvailableMapSources
and hit tab one more time
Result
public ObservableCollection<BaseTileSource> AvailableMapSources
{
get { return GetValue<ObservableCollection<BaseTileSource>>(
AvailableMapSourcesProperty); }
set { SetValue(AvailableMapSourcesProperty, value); }
}
public static readonly PropertyData AvailableMapSourcesProperty =
RegisterProperty("AvailableMapSources",
typeof(ObservableCollection<BaseTileSource>));
5.2.2. CurrentMap
The user wants to see a map. To know which map the user is currently viewing, a CurrentMap
property needs to be added.
Steps
- Use the vmprop code snippet
- Add description Gets or sets the current map, then hit tab
- Add type
BaseTileSource
, then hit tab
- Finally use the name
CurrentMap
and hit tab one more time
Result
public BaseTileSource CurrentMap
{
get { return GetValue<BaseTileSource>(CurrentMapProperty); }
set { SetValue(CurrentMapProperty, value); }
}
public static readonly PropertyData CurrentMapProperty =
RegisterProperty("CurrentMap", typeof(BaseTileSource));
5.2.3. MapCenter
By default, Bing Maps shows the world. But you want to center the map to a specific location. This is represented by the MapCenter
property.
Steps
- Use the vmprop code snippet
- Add description Gets or sets the map center, then hit tab
- Add type
GeoCoordinate
, then hit tab
- Finally use the name
MapCenter
and hit tab one more time
- Add
using System.Device.Location
Result
public GeoCoordinate MapCenter
{
get { return GetValue<GeoCoordinate>(MapCenterProperty); }
set { SetValue(MapCenterProperty, value); }
}
public static readonly PropertyData MapCenterProperty =
RegisterProperty("MapCenter", typeof(GeoCoordinate));
5.2.4. ZoomLevel
The last property being registered is the ZoomLevel
property. A user might want to zoom into his or her own house. To track the level at which the user is currently zooming, this property is required.
Steps
- Use the vmprop code snippet
- Add description Gets or sets the zoom level, then hit tab
- Add type
double
, then hit tab
- Finally use the name
ZoomLevel
and hit tab one more time
Result
public double ZoomLevel
{
get { return GetValue<double>(ZoomLevelProperty); }
set { SetValue(ZoomLevelProperty, value); }
}
public static readonly PropertyData ZoomLevelProperty =
RegisterProperty("ZoomLevel", typeof(double));
5.3. Methods
Registering the properties was quite easy. The only method we have to implement now is the constructor. It initializes the properties such as AvailableMapSources
. Below is the code:
public MainPageViewModel()
: base()
{
AvailableMapSources = new ObservableCollection<BaseTileSource>
{
new BingAerial{ Name = "Bing Aerial"},
new BingRoad {Name = "Bing Road"},
new Mapnik {Name = "OSM Mapnik"},
new OsmaRender {Name = "OsmaRender"},
new Google {Name = "Google Hybrid", MapType = GoogleType.Hybrid},
new Google {Name = "Google Street", MapType = GoogleType.Street},
};
if (AvailableMapSources.Count > 0)
{
CurrentMap = AvailableMapSources[0];
}
ZoomLevel = 1;
}
The code initializes the available map sources (direct instantiation of the different types). It is possible to make this more dynamic, but that would make this example more complex. When the maps are initialized, it selects the first by default so the user has a map to stare at when the application starts. Finally, the ZoomLevel
is set to 1 which provides a full overview of the earth. Double clicking on a location will zoom in.
5.4. Commands
Let’s give the user the option to switch between different types of maps. As you probably remember from the previous chapter, we have created two buttons on the page. Let’s implement those commands. The commands should be located in the Commands region.
5.4.1. PreviousMap
As the name already suggests, the user can go to the previous map type (one down in the collection).
- Use the vmcommandwithcanexecute code snippet
- Add name
PreviousMap
, the hit tab
- Move the instantiation of the command to the beginning of the constructor
- Implement
CanExecute
with the following content:
return AvailableMapSources.Count > 1;
- Implement
Execute
with the following content:
var newIdx = AvailableMapSources.IndexOf(CurrentMap) - 1;
CurrentMap = AvailableMapSources[newIdx < 0 ? AvailableMapSources.Count - 1 : newIdx];
5.4.2. NextMap
As the name already suggests, the user can go to the next map type (one up in the collection).
- Use the vmcommandwithcanexecute code snippet
- Add name
NextMap
, the hit tab
- Move the instantiation of the command to the beginning of the constructor
- Implement
CanExecute
with the following content:
return AvailableMapSources.Count > 1;
- Implement
Execute
with the following content:
var newIdx = AvailableMapSources.IndexOf(CurrentMap) + 1;
CurrentMap = AvailableMapSources[newIdx > AvailableMapSources.Count - 1 ? 0 : newIdx];
5.5. Getting geographical locations
The user can now zoom in and switch between maps. Great, but the user wants to see his or her current location by default. But how can we get the current geographical location in an MVVM situation? No problem, Catel to the rescue!
Catel provides an ILocationService
which is available on Windows Phone 7 by default. In this article, we are going to use this service to retrieve the current location of the user and update it if necessary. The ILocationService
needs to be started so real-time updates can be provided.
In the constructor, retrieve the service and start it:
var locationService = GetService<ILocationService>();
locationService.LocationChanged += OnCurrentLocationChanged;
locationService.Start();
In the Methods region, add the implementation of the OnCurrentLocationChanged
event handler:
private void OnCurrentLocationChanged(object sender, LocationChangedEventArgs e)
{
if (e.Location != null)
{
MapCenter = new GeoCoordinate(e.Location.Latitude,
e.Location.Longitude, e.Location.Altitude);
}
}
As soon as ILocationService
provides the view model with a new location, the current MapCenter
is updated to that location to provide the user a real-time map overview.
The location service needs to be shut down when you are finished using it. This is where the Close
method of the ViewModelBase
of Catel really comes in handy. Override the Close
method in the following way:
protected override void Close()
{
var locationService = GetService<ILocationService>();
locationService.LocationChanged -= OnCurrentLocationChanged;
locationService.Stop();
base.Close();
}
It is very important to call base.Close
since it will further handle the closing of the page. As you can see, the ILocationService
is retrieved again (same instance), the view model unsubscribes from the event, and finally stops the service.
To give the user a great user experience, let’s make sure the map is already zoomed in as close to earth as possible. To do this, change the default value for ZoomLevel
to 19 in the constructor.
How to make map center updates work?
When you run the app, you will notice that maps do not update. This is because the Map
control does not listen to changes correctly. This is a really bad implementation by Microsoft. Luckily, Catel can handle such cases and has a solution for everything. In the code-behind of MainPage
, we can be notified of changes inside the ViewModel (so the view-model still is not aware of any UI), and the application can provide real-time updates. This has nothing to do with MVVM or Catel, it’s just a bad implementation of the Map
control.
To complete the implementation, add a dependency property named MapCenter
like in the following code example (defined using the propdp code snippet):
public GeoCoordinate MapCenter
{
get { return (GeoCoordinate)GetValue(MapCenterProperty); }
set { SetValue(MapCenterProperty, value); }
}
public static readonly DependencyProperty MapCenterProperty =
DependencyProperty.Register("MapCenter", typeof(GeoCoordinate),
typeof(MainPage), new PropertyMetadata(
null, (sender, e) => ((MainPage)sender).UpdateMapCenter()));
As you can see, it uses the UpdateMapCenter
of the MainPage
, so here is the implementation of that method:
private void UpdateMapCenter()
{
map.SetView(ViewModel.MapCenter, ViewModel.ZoomLevel);
}
Great, but now we need to make sure that we “magically” link the view model to the view. In Catel, you can do this via simple attributes. Decorate the property using ControlToViewModelAttribute
:
[ControlToViewModel(MappingType = ControlViewModelModelMappingType.ViewModelToControl)]
public GeoCoordinate MapCenter
PhoneApplicationPage
will watch the view model and update the MapCenter
property of the MainPage
as soon as a change occurs.
5.6. Simulating geographical locations
Great, the application seems complete now. But your testers are using a simulator instead of a real Windows Phone 7. No problem, Catel fully supports test implementations of the ILocationService
so you can simulate real-time GPS updates in your emulator.
To do this, you need to configure the IoC container. Below is a method that does all of this for you, and it also adds a route to walk through a street:
private void InitializeDemoRoute()
{
IoC.IoCProvider.Instance.RegisterType<ILocationService,
MVVM.Services.Test.LocationService>();
var testLocationService =
(MVVM.Services.Test.LocationService)GetService<ILocationService>();
TimeSpan timeSpan = new TimeSpan(0, 0, 0, 0, 500);
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38772d, 5.56484d),
new TimeSpan(0, 0, 0, 5)));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38771d, 5.56484d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38770d, 5.56484d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38769d, 5.56483d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38768d, 5.56483d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38767d, 5.56483d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38766d, 5.56482d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38765d, 5.56482d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38764d, 5.56482d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38763d, 5.56481d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38762d, 5.56481d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38761d, 5.56481d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38760d, 5.56480d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38759d, 5.56480d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38758d, 5.56480d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38757d, 5.56479d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38756d, 5.56479d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38755d, 5.56479d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38754d, 5.56478d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38753d, 5.56478d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38752d, 5.56478d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38751d, 5.56477d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38750d, 5.56477d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38749d, 5.56477d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38748d, 5.56476d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38747d, 5.56476d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38746d, 5.56476d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38745d, 5.56475d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38744d, 5.56475d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38743d, 5.56475d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38742d, 5.56474d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38741d, 5.56474d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38740d, 5.56474d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38739d, 5.56473d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38738d, 5.56473d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38737d, 5.56473d), timeSpan));
}
Looks like a lot of code. There are of course smarter ways to write this, but this way the user doesn’t have to understand lots of for
-loops, but can easily understand what is going on. I will leave the creation of a smarter way to do this up to the reader as an exercise.
The method starts by registering the test version of the ILocationService
in the IoC container. Then, it queues all the expected locations to the service that will start spawning the changes as soon as the service is started (like a normal GPS device would do as well).
The last thing left to do is to call the InitializeDemoRoute
in the constructor. Do it before retrieving the service using GetService
, because at that point, the view model decides whether to use the test or production version of the service.
6. Conclusion
This article shows how easy it is to write MVVM applications for Windows Phone 7 using Catel, even when you are in need of reading and simulating the geographical location.
There are some downsides to using the generic base classes for the user controls that are provided by Catel in Silverlight. The problem is that Silverlight (and thus Windows Phone 7) do not allow a page or control to derive directly from a generic class. The project and item templates take care of this for you as a developer though.
Did it all go too fast, or are you not able to link the pieces together? Don’t worry! We are here to help you with your problems regarding Catel. You can either post a comment at this article or go to the discussion pages at CodePlex.
We’d love to hear your feedback, so if you have any, don’t hesitate to let us know! Thanks for reading and hopefully we will meet again in the next article about Catel!