Maps With MVVM on Windows Phone 8.1: The Complete Reference

Usually in a regular day, everyone would've loved to make a map app. I mean, it's seriously easy considering the easy drag and drop of the map control and hooking up some boilerplate code to detect my own location and what not. For starters that's okay, but when you do end up rooting for MVVM, you are bound to look for ways to make your map app work on MVVM too. This is a primer for people who love to do their stuff in MVVM and loves maps too.
 
Let's move ahead and create a new project for Windows Phone. We are using the Laurent Bugnions infamous MVVMLight Toolkit for our MVVM purposes. For those unfamiliar with that:
  1. Please proceed towards your Solution Explorer.
  2. And go over to the references and right-click on that to go to Manage Nuget Packages. 
  3. Now you can type in "mvvmlight" in the search and when it pops up get it installed.

Now, the moment you install mvvmlight you are bound to see a folder named ViewModel popped up in your directory tree.

 
 
And it contains MainViewModel and ViewModelLocator classes. Now, as in the basic design pattern of MVVMLight suggests, ViewmodelLocator is the key to locate the possible ViewModels over the entire app. If you look into the ViewModelLocator you will see.
  1. public class ViewModelLocator  
  2.     {  
  3.         /// <summary>  
  4.         /// Initializes a new instance of the ViewModelLocator class.  
  5.         /// </summary>  
  6.         public ViewModelLocator()  
  7.         {  
  8.             ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);  
  9.   
  10.             SimpleIoc.Default.Register<MainViewModel>();  
  11.         }  
  12.   
  13.         public MainViewModel Main  
  14.         {  
  15.             get  
  16.             {  
  17.                 return ServiceLocator.Current.GetInstance<MainViewModel>();  
  18.             }  
  19.         }  
  20.           
  21.         public static void Cleanup()  
  22.         {  
  23.             // TODO Clear the ViewModels  
  24.         }  
  25.     } 
So, that's some boilerplate code already being hooked up so you can make your desired MVVM app in style. The MainViewmodel is already being registered here in the SimpleIoc and you can access it using the Main property. So, let's go over our MainPage.xaml and set it's DataContext to MainViewModel, remember we are using ViewModelLocator and access its Main property.
 
So, let's go ahead and add a DataContext property to the page as in the following:
  1. DataContext="{Binding Main, Source={StaticResource Locator}}" 
 Now, before you scream, where is the locator, it's right in your App.xaml as in the following:
  1. <Application.Resources>  
  2.     <vm:ViewModelLocator x:Key="Locator" xmlns:vm="using:MVVMMaps.ViewModel" />  
  3.   </Application.Resources> 
So, this is how MVVMLight uses the ViewModelLocator to help us locate the MainViewModel.
 
Now as we have hooked up our MainPage.xaml with MainViewModel the next thing to do is to bring our favourite Map control to our Main Grid.
  1. Grid>  
  2.         <Maps:MapControl >  
  3.               
  4.         </Maps:MapControl>  
  5.     </Grid> 
Now,  we'd like to detect our own location whenever the app starts and let's put some boilerplate code for that. Let's move to our MainViewModel.cs to put some basic boilerplate location detection code. But before doing that let's add a bindable property so we can bind our own location to the map control. Usually if you have MVVMLight toolkit installed in your PC you'd find it's code snippets pretty handy in this case. Just go ahead and type mvvminpc and it would create a bindable property for you. :)
  1. public const string MyLocationPropertyName = "MyLocation";  
  2.   
  3. private Geopoint _myLocation = null;  
  4.   
  5. public Geopoint MyLocation  
  6. {  
  7.     get  
  8.     {  
  9.         return _myLocation;  
  10.     }  
  11.   
  12.     set  
  13.     {  
  14.         if (_myLocation == value)  
  15.         {  
  16.             return;  
  17.         }  
  18.   
  19.           
  20.         _myLocation = value;  
  21.         RaisePropertyChanged(MyLocationPropertyName);  
  22.     }  

Now that we have our bindable property to bind our location we need to detect our current location. So without further delay, let's put a very simple location detection async method in place to save ourselves.
  1. private async void GetMyLocation()  
  2. {  
  3.     try  
  4.     {  
  5.         Geolocator locator = new Geolocator();  
  6.           
  7.         var location = await locator.GetGeopositionAsync(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(60));  
  8.         MyLocation = location.Coordinate.Point;  
  9.     }  
  10.     catch (Exception ex)  
  11.     {  
  12.   
  13.         MessageDialog Dialog = new MessageDialog("Location detection failed");  
  14.         Dialog.ShowAsync();  
  15.     }  

Now, that's pretty simple and standard to be exact. I have put a simple MessageDialog in place just to ensure that I get to see if anything goes wrong. We will definitely make it better a bit later.
 
Now, it's time to put some markers on the map. Unlike the MapIcon class that is available to be used in cases likes these, those are not bindable. So, I will put some custom XAML markers inside the Map and bind it . 
 
Let's put a basic Grid and a Textblock inside it to show our Location on the Map and bind the location to our bindable property MyLocation from our MainViewModel.
  1. <Grid>  
  2.         <Maps:MapControl Center="{Binding MyLocation}" >  
  3.             <Grid Background="#FFFF4444" Maps:MapControl.Location="{Binding MyLocation}">  
  4.                 <TextBlock  Text="Test" FontFamily="20" Foreground="White" ></TextBlock>  
  5.             </Grid>  
  6.         </Maps:MapControl>  
  7.     </Grid> 
Now, there's really something to look at. The first thing worth noticing is we have just declared our Grid with a TextBlock inside the MapControl itself. We have bound our Grid's position with the Maps:MapControl.Location property binding it to MyLocation. We even bound our MapControl's center to the same Location. Now we need a couple of things more. Let's put a little ProgressIndicator in the status bar to show the user that we are trying to track down their location.
  1. private async void ToggleProgressBar(bool toggle, string message="")  
  2. {  
  3.     var statusBar = Windows.UI.ViewManagement.StatusBar.GetForCurrentView();  
  4.       
  5.   
  6.     if (toggle)  
  7.     {  
  8.         statusBar.ProgressIndicator.Text = message;  
  9.         await statusBar.ProgressIndicator.ShowAsync();  
  10.     }  
  11.     else  
  12.     {  
  13.         await statusBar.ProgressIndicator.HideAsync();  
  14.     }  

Now it's a little method to get the statusbar of the current view, put a text over it and show it. You might tjhink that we're done, but not quite yet. We need to focus the map to the proper zoom level. One way to do that would be to just bind the ZoomLevel of the map control from MainViewModel.cs. But I do want to keep the UI logic inside the xaml.cs plus I want the sweet animation it makes when the MapControl sets the view to a specific location. So, to do that, let's go over our MainPage.xaml.cs and put a method to do that. 
  1. private async void SetMapView(Geopoint point)  
  2.         {  
  3.             await MainMap.TrySetViewAsync(point, 15,0,0,Windows.UI.Xaml.Controls.Maps.MapAnimationKind.Bow);  
  4.         } 
Now, the question would be, how to hook this up from the viewmodel? Messaging to the rescue! Since we want to trigger this from the viewmodel all we need to do is, put a Messenger object in the viewmodel, send a message with a predefined token and a parameter, if needed, and receive the message in the MainPage.xaml. Be assured you need to have a messenger registered in MainPage.xaml.cs.
 
Now let's register the MainPage.xaml.cs to the default messenger provided by MVVMLight. This is why I love MVVMLight, you see?
  1. Messenger.Default.Register<Geopoint>(this, Constants.SetMapViewToken, SetMapView); 
Just put this line inside the MainPage.xaml.cs public constructor MainPage() and you're done! You can definitely see that I have used a string Constants.SetMapViewToken as a token to identify the message and assigned SetMapView as an action.
 
If you're curious about  Constants.SetMapViewToken, this will clear things up for you:
  1. internal class Constants  
  2.     {  
  3.         public static string SetMapViewToken = "SetMapView";  
  4.     } 
It's just a string to identify my message. Now, let's add up a command bar/application bar at the bottom and add a button to trigger the location tracking instead of doing that in the MainViewModel constructor. 
  1. Page.BottomAppBar>  
  2.         <CommandBar>  
  3.             <AppBarButton Label="Find Me!" Icon="Target" />  
  4.         </CommandBar>  
  5.     </Page.BottomAppBar> 
Now, we need a command to bind the AppBarButton to that very command in MainViewModel. To do that let's create a RelayCommand, a very cool ICommand implementation in MVVMLight and put our location detection method as an action to that command.  So, let's create a command first.
  1. public const string GetMyLocationCommandPropertyName = "GetMyLocationCommand";  
  2.   
  3. private RelayCommand _getMyLocationCommand = null;  
  4.   
  5. public RelayCommand GetMyLocationCommand  
  6. {  
  7.     get  
  8.     {  
  9.         return _getMyLocationCommand;  
  10.     }  
  11.   
  12.     set  
  13.     {  
  14.         if (_getMyLocationCommand == value)  
  15.         {  
  16.             return;  
  17.         }  
  18.   
  19.          
  20.         _getMyLocationCommand = value;  
  21.         RaisePropertyChanged(GetMyLocationCommandPropertyName);  
  22.     }  

And let's use our GetMyLocation as the action for the command as in the following:
  1. public MainViewModel()  
  2. {  
  3.       
  4.     GetMyLocationCommand = new RelayCommand(GetMyLocation);  
  5.       

And still there is one thing to do before we try it out, our Grid with a Textblock to show my location on the map is really small in size, let's ramp up that size and expand the view a bit.
  1. <Grid>  
  2.         <Maps:MapControl  x:Name="MainMap" Center="{Binding MyLocation}" >  
  3.             <Grid Background="#FFFF4444" Maps:MapControl.NormalizedAnchorPoint="0.5,1" Maps:MapControl.Location="{Binding MyLocation}">  
  4.                 <TextBlock  Text="Here I Am!" FontFamily="65" Margin="10" Foreground="White" ></TextBlock>  
  5.             </Grid>  
  6.         </Maps:MapControl>  
  7.     </Grid> 
Now, apart from the font size and margin change we have a new property set and that is named Maps:MapControl.NormalizedAnchorPoint. It sets the anchor of the XAML control you are using as a pushpin, I want mine to be anchored in the middle bottom thus I set it to (0.5, 1). If you want to learn more about the Map Control itself, you might want to read my previous article here.
 
Now, let's start up our emulator and watch some magic running!
 
 
 
Now, I believe, like everyone, you can already see a problem, the pushpin that we made is hanging over there before it gets the location. Now, what we need is to make that pushpin not visible until we find our location. ;)
 
 
 
Now, that's what it looks like when it's fixed. The proper MVVM approach would be to do this using a dataconverter and toggle it in and out from the viewmodel. But in this case since this will only be toggled once, let's just toggle the visibility when the map view is being set.
  1. private async void SetMapView(Geopoint point)  
  2.         {  
  3.             MyLocationPushpin.Visibility = Visibility.Visible;  
  4.             await MainMap.TrySetViewAsync(point, 15,0,0,Windows.UI.Xaml.Controls.Maps.MapAnimationKind.Bow);  
  5.         } 
And of course, I named the Pushpin Grid to MyLocationPushpin in XAML to access that from xaml.cs as in the following:
  1. <Grid Visibility="Collapsed" x:Name="MyLocationPushpin" Background="#FFFF4444" Maps:MapControl.NormalizedAnchorPoint="0.5,1" Maps:MapControl.Location="{Binding MyLocation}">  
  2.                 <TextBlock  Text="Here I Am!" Margin="10" Foreground="White" ></TextBlock>  
  3.             </Grid> 
Now, with that out of the way we have a working Location finder done with MVVM. Let's notch up the ante a bit, shall we?
 
An Interactive PushPin on the map
 
Now usually all our PushPins are static and don't really animate or interact. Now, we need them to animate. Let's proceed and do that. So, first of all, I made some changes to the existing PushPin to make it a bit classier. Now it kind of looks like the following:
 
 
 
Now, if you want to have a peek at the XAML for it, it is like the following:
  1. <StackPanel  x:Name="MyLocationPushpin"    
  2.                 Maps:MapControl.NormalizedAnchorPoint="0.5,1"   
  3.                 Maps:MapControl.Location="{Binding MyLocation.Coordinate.Point}"   
  4.                 Width="Auto" Height="Auto"  >  
  5.                 <Grid x:Name="ContentGrid"  Height="Auto" Width="Auto">  
  6.   
  7.                     <Rectangle Fill="#FFFF4444" Height="Auto" Width="Auto" RadiusX="3" RadiusY="3"/>  
  8.   
  9.                     <StackPanel HorizontalAlignment="Center" Margin="5" >  
  10.                         <TextBlock x:Name="LocationName"  Text="Here I Am!" FontSize="14" Foreground="White" HorizontalAlignment="Center" />  
  11.                         <Grid x:Name="MainPushPinDetails" Height="0">  
  12.                             <TextBlock x:Name="AddressText" Text="I'm seriously here!" HorizontalAlignment="Center"/>  
  13.                           
  14.                         </Grid>  
  15.                       
  16.                     </StackPanel>  
  17.                       
  18.                 </Grid>  
  19.                 <Path Data="M33.4916,35.3059 L42.1937,35.3059 L38.1973,40.6036 z" Fill="#FFFF4444" HorizontalAlignment="Center" Height="6.302" Margin="0,-1,0,0" Stretch="Fill" UseLayoutRounding="False" Width="9.702"/>  
  20.             </StackPanel> 
I've changed the main container to a stackpanel rather than a grid and made some changes alongside with putting a rounded rectangle in the background to provide it it's current look. Now, what I want is, when a user taps on this PushPin, it's going to slide up and show some extra info. Now usually for a guy from Windows Phone 8, an expander control is a best choice if you ever used Windows Phone Toolkit before. You can even for an open source expander control for your Windows Phone 8.1 app here. And one more thing to add here will be although I have a textblock named AddressText and I wrote some text on it, it is not visible on the screenshot. Thats what I will slide up and show the user when he taps.
 
I have a simple hack to do it myself since I want to enable my PushPin to have possibly many kinds of animation and I want to define my storyboards for it. So I went ahead to blend and created 2 storyboards, one will be used to slide open the pushpin and the other will slide back and they will look like the following:
  1. <Storyboard x:Name="OpenPushPin">  
  2.             <DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="MainPushPinDetails">  
  3.                 <EasingDoubleKeyFrame KeyTime="0" Value="0"/>  
  4.                 <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="15">  
  5.                     <EasingDoubleKeyFrame.EasingFunction>  
  6.                         <CubicEase EasingMode="EaseOut"/>  
  7.                     </EasingDoubleKeyFrame.EasingFunction>  
  8.                 </EasingDoubleKeyFrame>  
  9.             </DoubleAnimationUsingKeyFrames>  
  10.         </Storyboard>  
  11.           
  12.   
  13.         <Storyboard x:Name="ClosePushPin">  
  14.             <DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="MainPushPinDetails">  
  15.                 <SplineDoubleKeyFrame KeyTime="0" Value="15"/>  
  16.                 <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0">  
  17.                     <EasingDoubleKeyFrame.EasingFunction>  
  18.                         <CubicEase EasingMode="EaseOut"/>  
  19.                     </EasingDoubleKeyFrame.EasingFunction>  
  20.                 </EasingDoubleKeyFrame>  
  21.             </DoubleAnimationUsingKeyFrames>  
  22.         </Storyboard> 
Now, you can definitely see that these are Dependent Animations, so I'd keep these limited in my app. Now, everyone wants to see animation in action, not in XAML. :D Before going into action, we must determine a way to switch these animations. The thing is when the PushPin opens it will play OpenPushPin and when it closes it will play ClosePushPin. So, I need to have a boolean trigger somewhere to change this animations in time. Now, I can easily do this by putting a bool value in my viewmodel but I want to keep the UI logic totally separate from my ViewModel or at least as much as possible. So, I found a hack.
 
My hack was pretty straightforward. I actually put a checkbox all over the PushPin but with an empty ControlTemplate defined in its style. So, it will basically look like an empty grid when it actually is a checkbox.
 
Now my Map Pushpin XAML looks like the following:
  1. <StackPanel  x:Name="MyLocationPushpin"    
  2.                 Maps:MapControl.NormalizedAnchorPoint="0.5,1"   
  3.                 Maps:MapControl.Location="{Binding MyLocation.Coordinate.Point}"   
  4.                 Width="Auto" Height="Auto"  >  
  5.                 <Grid x:Name="ContentGrid"  Height="Auto" Width="Auto">  
  6.   
  7.                     <Rectangle Fill="#FFFF4444" Height="Auto" Width="Auto" RadiusX="3" RadiusY="3"/>  
  8.   
  9.                     <StackPanel HorizontalAlignment="Center" Margin="5" >  
  10.                         <TextBlock x:Name="LocationName"  Text="Here I Am!" FontSize="14" Foreground="White" HorizontalAlignment="Center" />  
  11.                         <Grid x:Name="MainPushPinDetails" Height="0">  
  12.                             <TextBlock x:Name="AddressText" Text="I'm seriously here!" HorizontalAlignment="Center"/>  
  13.                           
  14.                         </Grid>  
  15.                       
  16.                     </StackPanel>  
  17.   
  18.                     <CheckBox x:Name="CheckBoxGrid" Content="CheckBox" Style="{StaticResource CheckGridStyle}" MinWidth="0" Width="{Binding ActualWidth, ElementName=ContentGrid}" Height="{Binding ActualHeight, ElementName=ContentGrid}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0" Background="#FFFB0606">  
  19.                         <Interactivity:Interaction.Behaviors>  
  20.                             <Core:EventTriggerBehavior EventName="Checked">  
  21.                                 <Media:ControlStoryboardAction Storyboard="{StaticResource OpenPushPin}"/>  
  22.                             </Core:EventTriggerBehavior>  
  23.                             <Core:EventTriggerBehavior EventName="Unchecked">  
  24.                                 <Media:ControlStoryboardAction Storyboard="{StaticResource ClosePushPin}"/>  
  25.                             </Core:EventTriggerBehavior>  
  26.                         </Interactivity:Interaction.Behaviors>  
  27.                     </CheckBox>  
  28.                       
  29.                 </Grid>  
  30.                 <Path Data="M33.4916,35.3059 L42.1937,35.3059 L38.1973,40.6036 z" Fill="#FFFF4444" HorizontalAlignment="Center" Height="6.302" Margin="0,-1,0,0" Stretch="Fill" UseLayoutRounding="False" Width="9.702"/>  
  31.             </StackPanel> 
Now, many questions might occur to you. Before going to those, allow me to show you the style of the CheckBox that makes it look like a blank grid; that is the following:
  1. <Style x:Key="CheckGridStyle" TargetType="CheckBox">  
  2.             <Setter Property="Template">  
  3.                 <Setter.Value>  
  4.                     <ControlTemplate TargetType="CheckBox">  
  5.                         <Grid x:Name="CheckGrid" Background="Transparent"/>  
  6.                     </ControlTemplate>  
  7.                 </Setter.Value>  
  8.             </Setter>  
  9.         </Style> 
Now, on the PushPin XAML you can see that I have gone over Blend again and used Blend's famous behaviours to trigger my Storyboards. I triggered OpenPushPin when the checkbox is checked and ClosePushPin when it's unchecked. The result may surprise you!
 
 
 
Looks like the hack worked! Cool, Huh? Now even you can do an animated interactive PushPin yourself. 
 
Showing a Collection of Pushpins on the Map

We've already seen quiet a lot on how you can actually show your own location in a Map Control following MVVM. Now we need to take it a bit further. Usually we end up having multiple PushPins in our maps in possibly all map apps. So, how can we facilitate that in this Map Control following MVVM?
 
Now, what I want to do is to determine my nearby food places around me. I'm using the Google places API for this purpose and I have made some utility classes. Although very oorly coded since this was made as quickly as possible.
 
The first utility class I added here is a very simple Google Places API wrapper just to get my job done:
  1. internal class FoodSearcher  
  2.     {  
  3.         private string BaseUrl = "https://maps.googleapis.com/maps/api/place/nearbysearch/";  
  4.         private string Format = "json";  
  5.         private string LocationKey = "location";  
  6.         private string RadiusKey = "radius";  
  7.         private string TypesKey = "types";  
  8.         private string Type = "food";  
  9.         private string KeyKey = "key";  
  10.         private string Key = "AIzaSyDSuWOakAmCKEzeDfPq3fRfuISKu0nhjmU";  
  11.   
  12.         public async Task<FoodLocations> SearchForFoodPlaces(Geopoint point, int radius)  
  13.         {  
  14.             try  
  15.             {  
  16.                 string searchUrlFormat = string.Concat(BaseUrl, Format, "?", LocationKey, "=""{0}""&""radius={1}""&", TypesKey, "=", Type, "&", KeyKey, "=", Key);  
  17.                 string searchUrl = string.Format(searchUrlFormat, GetLocationParam(point), radius);  
  18.   
  19.                 HttpClient client = new HttpClient();  
  20.                 var data = await client.GetStringAsync(searchUrl);  
  21.   
  22.                 return JsonConvert.DeserializeObject<FoodLocations>(data);  
  23.             }  
  24.             catch (System.Exception ex)  
  25.             {  
  26.                 return null;                
  27.             }  
  28.         }  
  29.   
  30.         private string GetLocationParam(Geopoint point)  
  31.         {  
  32.             return string.Concat(point.Position.Latitude.ToString(), ",", point.Position.Longitude.ToString());  
  33.         }  
  34.   
  35.     } 
All I did in here is I made a simple Google Places API request for the nearest food places and deserialized the JSON using Json.net. I used http://json2csharp.com/ for my JSON to C# classes translation purposes. And the model looks like the following:
  1. public class Location  
  2.     {  
  3.         public double lat { getset; }  
  4.         public double lng { getset; }  
  5.     }  
  6.   
  7.     public class Geometry  
  8.     {  
  9.         public Location location { getset; }  
  10.     }  
  11.   
  12.     public class OpeningHours  
  13.     {  
  14.         public bool open_now { getset; }  
  15.         public List<object> weekday_text { getset; }  
  16.     }  
  17.   
  18.     public class Photo  
  19.     {  
  20.         public int height { getset; }  
  21.         public List<object> html_attributions { getset; }  
  22.         public string photo_reference { getset; }  
  23.         public int width { getset; }  
  24.     }  
  25.   
  26.     public class FoodLocation : ObservableObject  
  27.     {  
  28.   
  29.         public const string LocationPropertyName = "Location";  
  30.   
  31.         private Geopoint _Location = null;  
  32.   
  33.         public Geopoint Location  
  34.         {  
  35.             get  
  36.             {  
  37.                 return _Location;  
  38.             }  
  39.   
  40.             set  
  41.             {  
  42.   
  43.                 _Location = value;  
  44.                 RaisePropertyChanged(LocationPropertyName);  
  45.             }  
  46.         }  
  47.   
  48.           
  49.         public Geometry _geometry;  
  50.         public Geometry geometry { get { return _geometry;  } set { _geometry = value; Location = new Geopoint(new BasicGeoposition() {Latitude=value.location.lat, Longitude=value.location.lng });  } }  
  51.         public string icon { getset; }         
  52.         public string name { getset; }  
  53.         public OpeningHours opening_hours { getset; }  
  54.         public string place_id { getset; }  
  55.         public double rating { getset; }  
  56.         public string reference { getset; }  
  57.         public string scope { getset; }  
  58.         public List<string> types { getset; }  
  59.         public string vicinity { getset; }  
  60.         public int? price_level { getset; }  
  61.         public List<Photo> photos { getset; }  
  62.   
  63.           
  64.     }  
  65.   
  66.     public class FoodLocations : ObservableObject  
  67.     {  
  68.         public List<object> html_attributions { getset; }  
  69.         public string next_page_token { getset; }  
  70.   
  71.         
  72.         public const string resultsPropertyName = "results";  
  73.   
  74.         private List<FoodLocation> _results = null;  
  75.   
  76.           
  77.         public List<FoodLocation> results  
  78.         {  
  79.             get  
  80.             {  
  81.                 return _results;  
  82.             }  
  83.   
  84.             set  
  85.             {  
  86.                  
  87.   
  88.                 _results = value;  
  89.                 RaisePropertyChanged(resultsPropertyName);  
  90.             }  
  91.         }  
  92.   
  93.           
  94.         public string status { getset; }  
  95.     } 
Although that looks seriously long, there's a very few thing to look into. You can definitely see that I declared FoodLocations and FoodLocation classes as ObservableObjects. This comes with MVVMLight and you can easily use this to make a class observable. I did that because I want to track certain property changes in those classes. If you look closely you will see I only put PropertyChanged method invocations in certain properties like results and Location. Because I need them to update the map when they get updates.
 
Now, we need to get these from our MainViewModel. We added one extra AppBarButton to facilitate this in our AppBar.
  1. <AppBarButton x:Name="NearestFoodPlacesButton" Label="Food Places Nearby" Icon="Emoji" Visibility="Collapsed" Command="{Binding FindNearestFoodPlacesCommand}">  
  2. </AppBarButton> 
You can definitely see the Button visibility is collapsed. I kept this that way because I want it to be visible only after my location has been detected.  You can control it straight from MainViewModel using Data Converters but I want to keep it simple from the UI perspective. So I added the logic in MainPage.xaml.cs:
  1. private async void SetMapView(Geopoint point)  
  2.         {  
  3.             MyLocationPushpin.Visibility = Visibility.Visible;  
  4.             NearestFoodPlacesButton.Visibility = Visibility.Visible;  
  5.             await MainMap.TrySetViewAsync(point, 15,0,0,Windows.UI.Xaml.Controls.Maps.MapAnimationKind.Bow);  
  6.         } 
The next thing obviously is to put up a RelayCommand to trigger this thing.
  1. public const string FindNearestFoodPlacesCommandPropertyName = "FindNearestFoodPlacesCommand";  
  2.   
  3.         private RelayCommand _FindNearestFoodPlacesCommand = null;  
  4.         public RelayCommand FindNearestFoodPlacesCommand  
  5.         {  
  6.             get  
  7.             {  
  8.                 return _FindNearestFoodPlacesCommand;  
  9.             }  
  10.   
  11.             set  
  12.             {  
  13.                 if (_FindNearestFoodPlacesCommand == value)  
  14.                 {  
  15.                     return;  
  16.                 }  
  17.   
  18.                 _FindNearestFoodPlacesCommand = value;  
  19.                 RaisePropertyChanged(FindNearestFoodPlacesCommandPropertyName);  
  20.             }  
  21.         } 
We need one more thing before we start writing the implementation of that very RelayCommand. We need an ObservableCollection to hold all the food places. And since this would be of FoodLocation type:
  1. public const string NearbyFoodPlacesPropertyName = "NearbyFoodPlaces";  
  2.   
  3.         private ObservableCollection<FoodLocation> _NearbyFoodPlaces = null;  
  4.   
  5.         public ObservableCollection<FoodLocation> NearbyFoodPlaces  
  6.         {  
  7.             get  
  8.             {  
  9.                 return _NearbyFoodPlaces;  
  10.             }  
  11.   
  12.             set  
  13.             {  
  14.                 if (_NearbyFoodPlaces == value)  
  15.                 {  
  16.                     return;  
  17.                 }  
  18.   
  19.                 _NearbyFoodPlaces = value;  
  20.                 RaisePropertyChanged(NearbyFoodPlacesPropertyName);  
  21.             }  
  22.         } 
All I must do now is to get these locations with what I have done now. Let's put one implementation for our FindNearestFoodPlacesCommand:
  1. public MainViewModel()  
  2.         {  
  3.               
  4.             GetMyLocationCommand = new RelayCommand(GetMyLocation);  
  5.             FindNearestFoodPlacesCommand = new RelayCommand(FindNearestFoodPlaces);  
  6.               
  7.         }  
  8.   
  9.         private async void FindNearestFoodPlaces()  
  10.         {  
  11.             ToggleProgressBar(true"Looking for food places around");  
  12.             try  
  13.             {  
  14.                   
  15.                 if (MyLocation != null)  
  16.                 {  
  17.                     FoodSearcher Searcher = new FoodSearcher();  
  18.                     var FoodPlaces = await Searcher.SearchForFoodPlaces(MyLocation.Coordinate.Point, 500);  
  19.   
  20.                     NearbyFoodPlaces = new ObservableCollection<FoodLocation>(FoodPlaces.results);  
  21.                 }  
  22.   
  23.   
  24.             }  
  25.             catch (Exception ex)  
  26.             {  
  27.                 MessageDialog Dialog = new MessageDialog("Nearby Food Places Search Failed");  
  28.                 Dialog.ShowAsync();  
  29.   
  30.             }  
  31.   
  32.             ToggleProgressBar(false);  
  33.         } 
If you look into the implementation, you will see its pretty straightforward considering all our groundwork behind. All I'm doing is I'm searching a list of food places around my location inside 500 meters. And I'm assigning the result property of the FoodLocations object to our bindable ObservableCollection named NearbyFoodPlaces. Now, all that is left is binding that from XAML.
  1. <Maps:MapItemsControl ItemsSource="{Binding NearbyFoodPlaces}" >  
  2.   
  3.                 <Maps:MapItemsControl.ItemTemplate>  
  4.                     <DataTemplate>  
  5.                         <StackPanel   
  6.                 Maps:MapControl.NormalizedAnchorPoint="0.5,1"   
  7.                 Maps:MapControl.Location="{Binding Location}"   
  8.                 Width="Auto" Height="Auto"  >  
  9.                             <Grid x:Name="FoodContentGrid"  Height="Auto" Width="Auto">  
  10.   
  11.                                 <Rectangle Fill="#FF3FC500" Height="Auto" Width="Auto" RadiusX="3" RadiusY="3"/>  
  12.   
  13.                                 <StackPanel HorizontalAlignment="Center" Margin="5" >  
  14.                                     <TextBlock x:Name="FoodLocationName"  Text="{Binding name}" FontSize="14" Foreground="White" HorizontalAlignment="Center" />                                   
  15.                                 </StackPanel>  
  16.   
  17.                                    
  18.                             </Grid>  
  19.                             <Path Data="M33.4916,35.3059 L42.1937,35.3059 L38.1973,40.6036 z" Fill="#FF3FC500" HorizontalAlignment="Center" Height="6.302" Margin="0,-1,0,0" Stretch="Fill" UseLayoutRounding="False" Width="9.702"/>  
  20.                         </StackPanel>  
  21.                     </DataTemplate>  
  22.                 </Maps:MapItemsControl.ItemTemplate>  
  23.             </Maps:MapItemsControl>  
  24.               
  25.            
Now, what I did here is I instantiated Maps:MapItemsControl inside MapControl and set its ItemSource to our NearbyFoodPlaces ObervableCollection. Now if you remember our GeoPoint property in the FoodLocation class was Location and thus we assigned Maps:MapControl.Location="{Binding Location}" in the root stackpanel so it gets rendered properly.
 
So, what are we waiting for? It's time to check out how that looks. It's time for the magic!
 
 
 
Now, for our last and final trick we will hook up a command in the pushpin to invoke one action from the viewmodel. I will keep this really simple. I will show some extra data based on the pushpin tapped/clicked.  I added a new RelayCommand named ShowFoodLocationDetails and it would take a FoodLocation object as a command parameter because it would be passed from that Map Pushpin since they are actually a FoodLocation object. I instantiated an action for the command and showed some extra info in a MessageDialog. From MainViewModel.cs it looks like the following:
  1. public MainViewModel()  
  2.         {  
  3.               
  4.             GetMyLocationCommand = new RelayCommand(GetMyLocation);  
  5.             FindNearestFoodPlacesCommand = new RelayCommand(FindNearestFoodPlaces);  
  6.             ShowFoodLocationDetails = new RelayCommand<FoodLocation>(ShowFoodLocationDetailsAction);  
  7.               
  8.         }  
  9.   
  10.         private void ShowFoodLocationDetailsAction(FoodLocation location)  
  11.         {  
  12.             MessageDialog Dialog = new MessageDialog(location.name+ "-"+location.vicinity);  
  13.             Dialog.ShowAsync();  
  14.         } 
And the trick actually lies in XAML since you are binding these from the datatemplate of a MapItemsControl.ItemTemplate as in the following:
  1. <Maps:MapItemsControl.ItemTemplate>  
  2.                     <DataTemplate>  
  3.                         <StackPanel   
  4.                 Maps:MapControl.NormalizedAnchorPoint="0.5,1"   
  5.                 Maps:MapControl.Location="{Binding Location}"   
  6.                 Width="Auto" Height="Auto"  >  
  7.   
  8.                         <Interactivity:Interaction.Behaviors>  
  9.                             <Core:EventTriggerBehavior EventName="PointerPressed">  
  10.                                 <Core:InvokeCommandAction Command="{Binding Main.ShowFoodLocationDetails, Source={StaticResource Locator}}" CommandParameter="{Binding }"  />  
  11.                             </Core:EventTriggerBehavior>  
  12.                         </Interactivity:Interaction.Behaviors>  
  13.                   
  14.   
  15.                             <Grid x:Name="FoodContentGrid"  Height="Auto" Width="Auto">  
  16.   
  17.                                 <Rectangle Fill="#FF3FC500" Height="Auto" Width="Auto" RadiusX="3" RadiusY="3"/>  
  18.   
  19.                                 <StackPanel HorizontalAlignment="Center" Margin="5" >  
  20.                                     <TextBlock x:Name="FoodLocationName"  Text="{Binding name}" FontSize="14" Foreground="White" HorizontalAlignment="Center" />                                   
  21.                                 </StackPanel>  
  22.   
  23.                                    
  24.                             </Grid>  
  25.                             <Path Data="M33.4916,35.3059 L42.1937,35.3059 L38.1973,40.6036 z" Fill="#FF3FC500" HorizontalAlignment="Center" Height="6.302" Margin="0,-1,0,0" Stretch="Fill" UseLayoutRounding="False" Width="9.702"/>  
  26.                         </StackPanel>  
  27.                     </DataTemplate>  

  28.                 </Maps:MapItemsControl.ItemTemplate> 
The only block we added here is the  Interactivity:Interaction.Behaviors block since it adds a Core:EventTriggerBehavior to the data template. When a PointerPressed event is triggered we are invoking a Core:InvokeCommandAction and we are binding ShowFoodLocationDetails as our command. The most important thing here to be noticed is that we used a Locator defined in App.xaml that is actually our ViewModelLocator instantiation to locate the MainViewModel. The reason we do it like this here is because as we are now inside the datatemplate for the MapItemsControl.ItemTemplate, it doesn't know MainViewModel since it's a datacontext. So we used a Locator to point to a MainViewModel accessed by the Main property in MainViewModel and bound the command in there. Since we are sending the entire FoodLocation object as a command parameter we just kept that as CommandParameter="{Binding }".
 
 Now you can even tap on a FoodLocation Pushpin!
 
 
 
 So, that was all. I hope this helps and I hope you guys like it! Stay Frosty!
 


Similar Articles