Implementing Model-View-ViewModel with the Bing Maps (Virtual Earth) Silverlight Map Control

by James Richards September 28, 2009


I’ve been looking at the Bing Maps (Virtual Earth) Silverlight Map Control and thinking about how to use it in a Silverlight application that implements the Model-View-ViewModel pattern. If you’re not familiar with this pattern, check out Shawn Wildermuth’s MSDN Article Model-View-ViewModel in Silverlight 2 Apps.

I’ve created a simple proof of concept application that implements the pattern with two Views: a Map based View and a Data Grid based View. The application displays a map of well known surf spots in the Los Angeles area, along with some basic information about each spot in an accompanying data grid.



There is no live example posted for this one, as Microsoft has not yet released the Silverlight Map Control with a “Go Live” license. But you can download source code from here to try it out on your own system.

Synchronizing Multiple Views with the ViewModel

Both Views work off the same ViewModel and stay synchronized via events that the ViewModel fires back to the Views after user gestures.


There are two main types of user gestures where this comes into play:

  1. When the map is first loaded or the user pan/zooms the map, the ViewModel is notified of the new map extent. The ViewModel then loads surf spots that fall within the new Map Extent from a Repository. An event is fired back to both Views and they update themselves to display only the surf spots that were loaded based on the new map extent.
  2. When a user clicks on a surf spot in either the map or the list, the ViewModel is notified of the newly selected surf spot. An event is fired back to both views and they update themselves to display the selected spot. 

In the next section, I’ll take a closer look at the first example.

Loading Surf Spots from the Repository

The xaml file for the SurfSpotsMapView markup contains just a Map with a specified ViewChangeStart event.


<UserControl x:Class="Map.Client.Views.SurfSpotsMapView"
    <Grid x:Name="LayoutRoot" Background="White">
        <m:Map Name="Map1" ViewChangeStart="Map_ViewChangeStart">          

The ViewChangeStart event fires whenever the map extent is about to change. This event generally coincides with the beginning of the animation to the new map extent. This provides an excellent opportunity to start loading the surf spots while the map is doing its animation thing.

private void Map_ViewChangeStart(object sender, MapEventArgs e)
    MapViewSpecification mvs = Map1.TargetView;
    LocationRect bounds = Map1.GetBoundingRectangle(mvs);
    viewModel.LoadSurfSpots(bounds.North, bounds.West, bounds.South, bounds.East);

In the Map_ViewChangeStart event, the map’s TargetView property contains the MapViewSpecification of the map extent that the map will be animating (panning or zooming) towards. The method examines the bounding rectangle of the target MapViewSpecification and asks the SurfSpotsViewModel to load the surf spots within that map extent via the LoadSurfSpots method.

Notice that the method just asks the ViewModel to load the surf spots, but it doesn’t expect an immediate response. Later, when the ViewModel has finished loading the data, it fires off an event telling both Views to refresh themselves with the new data. This allows for data to be loaded asynchronously, typically from a remote web service.

The SurfSpotsMapView class has a ViewModel property setter where we wire up the events that are expected to be fired from the ViewModel. This is called when the View is first created:

public SurfSpotsViewModel ViewModel
    get { return viewModel; }
        viewModel = value;

        // Wire up the View Model
        viewModel.SurfSpotsLoaded += new EventHandler(SurfSpotsLoadedHandler);
        viewModel.SurfSpotsLoadError += new EventHandler(SurfSpotsLoadErrorHandler);
        viewModel.SelectedSurfSpotChanged += new EventHandler(SelectedSurfSpotChangedHandler);

Like the ViewModel, the repository is also asynchronous in nature and the ViewModel’s LoadSurfSpots method simply delegates to an analogous method on the repository. Consider the following ISurfSpotsRespository interface:

public interface ISurfSpotRepository
    // Load all surf spots
    void LoadSurfSpots();

    // Load surf spots within the specified extent
    void LoadSurfSpots(double north, double west, double south, double east);

    // Event to fire when surf spots have been loaded
    event EventHandler<SurfSpotEventArgs> SurfSpotLoadingComplete;

    // Event to fire when there is an error loading surf spots
    event EventHandler<SurfSpotErrorEventArgs> SurfSpotLoadingError;

When the repository has finished loading the data, it fires the SurfSpotLoadingComplete event back to the ViewModel, which in turn fires its own event back to any listening Views.

To keep the example simple, ISurfSpotRespository is implemented as a mock in-memory repository called InMemorySurfSpotRepository. See the sample code download for the complete implementation. Note that a real web application would most likely load the data from a remote web service. See Johannes Kebeck’s Blog Post Database Connections with the Virtual Earth Silverlight MapControl CTP for a good example of how to do that.

Once the data loading is complete, the SurfSpotsLoadedHandler method of the SurfSpotsMapView class handles adding the new surf spots to the map:

private void SurfSpotsLoadedHandler(object sender, EventArgs e)
    foreach (SurfSpot s in viewModel.SurfSpots)
        Ellipse point = new Ellipse()
            Width = 10,
            Height = 10,
            Fill = new SolidColorBrush(Colors.Red),
            Opacity = 0.65,
            Tag = s
        point.MouseLeftButtonDown += new MouseButtonEventHandler(
        MapLayer.SetMapPosition(point, new Location(s.Latitude, s.Longitude));
        ToolTipService.SetToolTip(point, s.Name);

In the SurfSpotsListView, keeping the list updated is even easier. The SurfSpots property of the ViewModel is an ObservableCollection<SurfSpot>, so we just need to set the ItemsSource property of inner DataGrid and it will keep itself updated automatically. This happens in the ViewModel property setter for the SurfSpotsListView:

public SurfSpotsViewModel ViewModel
    get { return viewModel; }
        viewModel = value;

        // Wire up the View Model
        viewModel.SelectedSurfSpotChanged += new EventHandler(SelectedSurfSpotChangedHandler);
        viewModel.SurfSpotsLoaded += new EventHandler(SurfSpotsLoaded);
        viewModel.SurfSpotsLoadError += new EventHandler(SurfSpotsLoadErrorHandler);

        // Set the items source for the data grid. Since it is an
        // ObservableCollection<> we don't need to handle the SurfSpotsLoaded
        // event. The grid view will automatically keep itself updated.
        DataGrid1.ItemsSource = viewModel.SurfSpots;

Unit Tests

Of course, one of the main reasons for using the Model-View-ViewModel pattern is testability. I’ve included a simple unit test of the View Model class in the Tests project to illustrate the concept. This test is modeled after the tests found in Shawn Wildermuth’s sample code. I didn’t create a full array of tests, as it wasn’t necessary for the proof of concept. I’ll leave that as an exercise for the reader.

Wrap Up

This post has presented a proof of concept sample application using the Model-View-ViewModel pattern with the Microsoft Bing Maps (Virtual Earth) Silverlight Map Control CTP. ViewModel synchronization with multiple Views is demonstrated in the sample. While we covered some of the implementation details in this post, there is more to be found in the downloadable sample – so be sure to check it out!

If you would be interested in a follow up post covering some of the additional implementation details, please let me know in the comments.

Hope this helps!


Powered by BlogEngine.NET
Theme by Mads Kristensen | Modified by Mooglegiant
Creative Commons License This work is licensed under a Creative Commons Attribution 3.0 United States License.


James Richards

Hi, I'm James Richards the CTO and co-founder of Artisan Global LLC. We make location-aware mobile apps with maps. I'm the author of QuakeFeed and I helped launch Zaarly at LASW Feb 2011. I also enjoy surfing, snowboarding, golfing, yoga, and music. I love my family: Linda, Sequoya and our cats Remy and Twiggy. Thanks for stopping by, I hope you find something helpful here.

Subscribe by RSS   Follow me on Twitter   Connect on Facebook   View my profile on LinkedIn

Amazon Associates

Some of my posts may contain Amazon Associates links. I only include these links when it makes sense within the context of the article. If you are in the market for one of these items, please consider clicking on an affiliate link to make your purchase. You still get the same great deal from Amazon and it will help me pay for hosting and bandwidth. Thanks!