Using the Windows Phone 7 Bing Maps Control with MVVM Light

By | February 1, 2011

I’ve recently adopted MVVM Light as my MVVM framework of choice for both Silverlight and Windows Phone 7 development. Why use frameworks like MVVM Light? First and foremost, to achieve clear separation of concerns between the application UI and the backing data. While this goal seems fairly elementary, pulling this off in practice can be quite challenging due to how Microsoft has designed some of the native controls. The Bing Maps control is one good example.

In my scenario (which I would guess is a fairly common scenario), I am data binding multiple locations as push pins on the map. Clicking on a push pin should cause the application to navigate to a details page (a responsibility of the view), but also the corresponding view model for the details page needs to be notified of the location in context. This is where things get a little tricky and the MVVM Light EventToCommand behavior and Messenger features come to the rescue.

To begin, I’ve put together a simple map view shown below. Notice the two interaction triggers on the map push pin. The second trigger is the navigate action … straightforward enough. The first trigger is the MVVM Light EventToCommand behavior that invokes a command called ‘SelectCommand’ on the bound location.

<phone:PhoneApplicationPage
                            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                            xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
                            xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
                            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                            xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
                            xmlns:ic="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
                            xmlns:Maps="clr-namespace:Microsoft.Phone.Controls.Maps;assembly=Microsoft.Phone.Controls.Maps"
                            xmlns:gs="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP7"
                            xmlns:c="clr-namespace:Trentacular.Phone.Converters"
                            x:Class="Trentacular.Phone.MainPage"
                            FontFamily="{StaticResource PhoneFontFamilyNormal}"
                            FontSize="{StaticResource PhoneFontSizeNormal}"
                            Foreground="{StaticResource PhoneForegroundBrush}"
                            SupportedOrientations="Portrait"
                            Orientation="Portrait"
                            DataContext="{Binding Main, Source={StaticResource Locator}}">
 
    <phone:PhoneApplicationPage.Resources>
        <c:GeoCoordinateConverter x:Key="GeoCoordinateConverter" />
    </phone:PhoneApplicationPage.Resources>
 
    <Grid x:Name="LayoutRoot">
        <Maps:Map ZoomLevel="10" Center="{Binding Path=MapCenter, Mode=TwoWay}">
            <Maps:MapItemsControl ItemsSource="{Binding NearbyLocations}">
                <Maps:MapItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Maps:Pushpin Location="{Binding Converter={StaticResource GeoCoordinateConverter}}" >
                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="MouseLeftButtonDown">
                                    <gs:EventToCommand Command="{Binding Path=SelectCommand}" />
                                    <ic:NavigateToPageAction TargetPage="/Details.xaml"/>
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                        </Maps:Pushpin>
                    </DataTemplate>
                </Maps:MapItemsControl.ItemTemplate>
            </Maps:MapItemsControl>
        </Maps:Map>
    </Grid>
 
</phone:PhoneApplicationPage>

For the sake of brevity, I am going to skip the Main view model, which contains a MarkerViewModel-typed ObservableCollection property called NearbyLocations that gets bound to by the MapItemsControl’s ItemsSource property shown above.

Now lets take a look at the MarkerViewModel class that serves as the view model for a single location on the map represented by a push pin. Notice the implementation of the SelectCommand property, which simply fires a PropertyChangedMessage. This will allow the details view model which listens for this particular message to update its context when the push pin is clicked.

        public class MarkerViewModel : ILocation
        {
            public Marker Marker { get; set; }
 
            public double Latitude
            {
                get { return Marker.Latitude; }
            }
 
            public double Longitude
            {
                get { return Marker.Longitude; }
            }
 
            private ICommand _selectCommand;
            public ICommand SelectCommand
            {
                get
                {
                    if (_selectCommand == null)
                    {
                        _selectCommand = new RelayCommand(() =>
                            {
                                var messenger = Messenger.Default;
                                messenger.Send(
                                    new PropertyChangedMessage<Marker>(null, Marker, "SelectedMarker")
                                    );
                            });
                    }
                    return _selectCommand;
                }
            }
        }

Finally, in the constructor of the Details view model we register to listen for the Marker-typed PropertyChangedMessage message.

    public class DetailsViewModel : ViewModelBase
    {
        public DetailsViewModel()
        {
            if (IsInDesignMode)
            {
                Marker = new Marker
                {
                    ID = 1,
                    Title = "Design Mode Marker Title"
                };
            }
            else
            {
                Messenger.Default.Register>(this,
                    message =>
                    {
                        DispatcherHelper.CheckBeginInvokeOnUI(() =>
                        {
                            Marker = message.NewValue;
                        });
                    });
            }
        }
        public const string MarkerPropertyName = "Marker";
        private Marker _marker;
        public Marker Marker
        {
            get { return _marker; }
            set
            {
                if (_marker == value)
                    return;
                var oldValue = _marker;
                _marker = value;
                // Update bindings, no broadcast
                RaisePropertyChanged(MarkerPropertyName);
            }
        }
    }

To sum up, I’ve demonstrated two really great features of the MVVM Light framework - the EventToCommand behavior and the Messenger features - and how these features can be used to successfully pull off the MVVM pattern with the Windows Phone 7 Bing Maps control.

4 thoughts on “Using the Windows Phone 7 Bing Maps Control with MVVM Light

  1. Depechie

    Small question, I’ve been using the MVVM model for my Bing Maps too and I even added the pushpin when Tombstoning…
    When the program gets activated again, I can set the pushpin again, but the zoomlevel I added will not set correctly.
    Any thoughts on this?

  2. Trent Post author

    Are you setting your ZoomLevel to use two-way binding to your ViewModel?

  3. Brandon

    Thanks for posting! Saved me some time learning how to do this for the first time.

Leave a Reply

Your email address will not be published. Required fields are marked *