In the last post we introduced the helpers that are available in the Phone toolkit for the Map control in Windows Phone 8: the map control is much more powerful than the one that is available in Windows Phone 7, but it lacks some controls that were widely used, like pushpins. In the previous post we’ve seen how to add a pushpin and place it in a specific position: it was an interesting experiment, but not really useful, because most of the times we need to place many pushpins on the same layer.
Let’s see how to do it using some features that every Windows Phone developer should know: template and binding.
Back to work
Let’s imagine that we’re going to display some restaurants that are near the user’s location. For this reason, the first thing we need is a class to map this information:
public class Restaurant { public GeoCoordinate Coordinate { get; set; } public string Address { get; set; } public string Name { get; set; } }
After that, we need a way to define a template to display a collection of Restaurant’s object over the map: we’re going to use one of the helpers available in the toolkit.
<maps:Map x:Name="myMap"> <toolkit:MapExtensions.Children> <toolkit:MapItemsControl Name="RestaurantItems"> <toolkit:MapItemsControl.ItemTemplate> <DataTemplate> <toolkit:Pushpin GeoCoordinate="{Binding Coordinate}" Content="{Binding Name}" /> </DataTemplate> </toolkit:MapItemsControl.ItemTemplate> </toolkit:MapItemsControl> </toolkit:MapExtensions.Children> </maps:Map>
We use again the MapExtensions.Children that we’ve learned to use in the previous post. Then we introduce the MapItemsControl, which is a control that is able to display a collection of objects over the map. Think of it like a ListBox control, but in this case the elements that are part of the collection are displayed over the map, as a new layer. The most interesting feature of this control is that, like other controls that are used to display collections, it supports templating: we can define the template (the ItemTemplate property) of a single object, that will be used to render every object that is part of the collection. In this sample, we define as a template a Pushpin object, which Content and GeoCoordinate properties are in binding with the properties of the Restaurant class. This means that, in the code, we’re going to assign to the ItemSource property of the MapItemsControl a collection of restaurants: the control is going to render a Pushpin for every restaurant in the collection and will place it on the map, in the position defined by the GeoCoordinate property.
Here is how we do it:
public MainPage() { InitializeComponent(); Loaded+=MainPage_Loaded; } void MainPage_Loaded(object sender, RoutedEventArgs e) { ObservableCollection<Restaurant> restaurants = new ObservableCollection<Restaurant>() { new Restaurant { Coordinate = new GeoCoordinate(47.6050338745117, -122.334243774414), Address = "Ristorante 1" }, new Restaurant() { Coordinate = new GeoCoordinate(47.6045697927475, -122.329885661602), Address = "Ristorante 2" }, new Restaurant() { Coordinate = new GeoCoordinate(47.605712890625, -122.330268859863), Address = "Ristorante 3" }, new Restaurant() { Coordinate = new GeoCoordinate(47.6015319824219, -122.335113525391), Address = "Ristorante 4" }, new Restaurant() { Coordinate = new GeoCoordinate(47.6056594848633, -122.334243774414), Address = "Ristorante 5" } }; ObservableCollection<DependencyObject> children = MapExtensions.GetChildren(myMap); var obj = children.FirstOrDefault(x => x.GetType() == typeof(MapItemsControl)) as MapItemsControl; obj.ItemsSource = restaurants; myMap.SetView(new GeoCoordinate(47.6050338745117, -122.334243774414), 16); }
The first thing to highlight is that we’re going to use this code in the Loaded event of the page. This is required because, if we do it in the MainPage constructor, we risk that the code is executed before that every control in the page is rendered, so we may not have access yet to the MapItemsControl object.
First we create some fake restaurants to use for our test and we add it to a collection, which type is ObservableCollection<Restaurant>. Then we need to get a reference to the MapItemsControl object we’ve declared in the XAML, the one called RestaurantItems. Unlucky, due to the particular architecture of the MapExtensions helper provided with the toolkit, we can’t access to the control directly using the Name property, but we need to look for it in the collection of controls inside the Children property of the MapExtensions controls.
To do this, we use the MapExtensions.GetChildren method of the toolkit, passing as parameter our map control. This method returns an ObservableCollection<DependencyObject>, which is a collection of all the controls that have been added. In this case, by using a LINQ query and the GetType() method, we get a reference to the MapItemsControl object. We can use the FirstOrDefault statement because we’ve added just one MapItemsControl inside the MapExtensions.Children collection: once we have a reference, we cast it to the MapItemsControl (otherwise it would be a generic object, so we wouldn’t have access to the specific properties of the MapItemsControl class). Finally, we are able to access to the ItemsSource property, which holds the collection that is going to be displayed over the map using the ItemTemplate we’ve defined in the XAML: we simply assign to this property the fake ObservableCollection<Restaurant> we’ve created before.
For sake of simplicity, we also set the center of the map to the coordinates of the first restaurant: this way will be easier for us to test the code and see if every pushpin is displayed correctly.
And here is the final result:
In the end
As you can see, the mechanism is very simple: it’s the usual one that we already use every time we have a collection that we wants to display. We define a collection of objects (usually a ObservableCollection<T>, to have INotifyPropertyChanged support out of the box), we define an ItemTemplate in the XAML and we assign the collection to the ItemsSource property of the control. The only trivial thing, in this case, is to get a reference to the MapItemsControl object, since it can’t be referenced just by using the name as for every other control.
Here is the link to download a sample that recap most of the things we’ve seen in the latest two posts:
Hello,
Great post! I have 2 Windows Phone apps that use about the same thing as you have mentioned. However, in one my those 2 apps, I’m loading about ~200 pins on MainPage.Loaded and I’m getting the actual position of the user. The app freezes on that scenario. I’m have been in contact with a Microsoft engineer, but they didn’t find the cause yet. However, they provided me a workaround to get the current position. You can take a look at the workaround in the comments here: http://blogs.windows.com/windows_phone/b/wpdev/archive/2012/12/03/geoposition-advanced-tracking-scenarios-for-windows-phone-8.aspx
ArchieCoder
Thanks for sharing!
First of all, thx for some superb tutorials…;-)
I would like to use the map and pushpins using Caliburn micro..
But I cant really see how to do this, if you have your “Restaurants” in ModelView, since I cant find any bindable sources…
Do you have any clue on how to solve this..??
Thx in advance
Soren
And with ModelView I ofcause mean ViewModel..;-)
You are my hero. I was looking everywhere for a good example of how to add a collection of markers on a map in Windows Phone 8. There is a lot of bad information out there.
I greatly appreciate this accurate, detailed explanation.
I’m glad I’ve been able to help you 🙂
First, thanks a lot for the two articles, they are inspiring. As Erin Giffin said, there are lot of information out there more related to wp7 than wp8 and, personaly, I am a bit confused.
I hace a litle problem adding more content to the messagebox display in the MyPushpin_OnTap event. I tried to get access to three more string variables in my restaurants (Strig FoodType, and others) so they are displayed with the MessageBoxResult, but I can’t.
I have been looking for it seven daysn and seven nights with no luck at all. Would you kindly throw some light with that issue?
THanks in advance.
Hi, when you bind your list of Restaurant objects to the pushpin collection, the DataContext of a single pushpin becomes the Restaurant object that is in binding with the control.
You just need to manage the Tap event of the Pushpin in the DataTemplate and cast the DataContext of the sender object as a Restaurant.
Restaurant restaurant = (sender as Pushpin).DataContext as Restaurant;
Then you’re able to access to all the object’s properties.
THANK YOU! That totally worked! I was so stuck, it would never come to my mind that solution. Thanks again.
Hello qmatteoq.
I am Gaurav and am following your tutorial with interest here in New Delhi. I have reached a wall however.
You suggest using Mainpage_Loaded method to ensure all mainpage components are available before pushpins-related code is executed. I found this not to be the case in my app.
In my app, all components, including map, are loaded. Then I click a button to get GPS coordinates. Next, I click a button to find trees (like restaurants) close to my coordinates. It is only then that ObservableCollection is created and ItemsSource property assigned.
Hence, as I see it, all components are available beforehand – in my case, that is.
And, yet, my code doesn’t execute: no pushpins are seen.
How should I proceed?
Would appreciate your advise.
Warm regards,
Gaurav
Hi Guarav,
can you send me a repro of your issue? Unfortunately I’m not able to understand what’s going on just by reading the description of your issue.
Thanks!
Thanks for the prompt response, Matteo.
With regards to your request for a repro of my issue, you can find it described in detail (along with code) at the forum on social.msdn.microsoft.com. Here is the direct link: http://goo.gl/8lyev7
Look forward your suggestion.
Regards,
Gaurav
Hi Gaurav,
I know that my friend Joost already helped you with this issue.
Hi,
First of all thanks for this post. It was very useful to me. And my doubt is,
I have to change my pushpin image on pushpin selected event.
(e.x)
i have to change map_icon_2.png at runtime for the selection pushpin.
please help me.
Hi, you can subscribe to the Tap event of the pushpin and, in the event handler, use the sender parameter to get a reference to the pushpin and change its properties.
Hi Matteo,
Thank you or this useful post. However I’m stuck wit something and I can’t find the problem. If you have the time to elp out I would be very grateful. Let’s start with the code:
[code]
void client_GetHotspotInAreaCompleted(object sender, ServiceReference1.getHotspotInAreaCompletedEventArgs e)
{
List hs = e.Result;
ObservableCollection hotSpots = new ObservableCollection();
foreach (var hotspot in hs)
{
hotSpots.Add(new HotSpot()
{
Coordinate = new GeoCoordinate((hotspot.North + hotspot.South) / 2, (hotspot.East + hotspot.West) / 2),
Name = hotspot.Name,
Id = hotspot.Id,
LocationID = hotspot.LocationID,
Value = hotspot.Value
});
}
ObservableCollection children = MapExtensions.GetChildren(sampleMap);
var obj = children.FirstOrDefault(x => x.GetType() == typeof(MapItemsControl)) as MapItemsControl;
obj.ItemsSource = hotSpots;
}
[/code]
This code talks to an Azure wcf service to get some hotspots. It works when the application first launches but after changing my location and this method gets called the second time, it crashes on the last line “obj.ItemsSource = hotSpots” with a “An exception of type ‘System.InvalidOperationException’ occurred in Microsoft.Phone.Controls.Toolkit.DLL but was not handled in user code”.
Do you have any idea what could be the cause of this? Because we just call the same method with just some different data from the database(max 10 hotspots returned).
Thanks in advance!
Hi, can you reproduce the exception and tell me the description reported by Visual Studio? The debugger should tell you the cause of the InvalidOperationException. Thanks!
Ok I found the problem, apparently you cannot overwrite the itemsSource but you first have to clear it. This can be useful for other people too so maybe you can include it in the post. Here’s the last part of the method I posted above that has to be changed for it to keep on working:
ObservableCollection children = MapExtensions.GetChildren(sampleMap);
var obj = children.FirstOrDefault(x => x.GetType() == typeof (MapItemsControl)) as MapItemsControl;
if (obj != null && obj.ItemsSource != null)
{
(obj.ItemsSource as IList).Clear(); // clear old collection
obj.ItemsSource = null;
}
obj.ItemsSource = hotSpots;
Without that comment the example is not functional at all! Thanks vannickgeerts.
Hi, how can I get index of selected pushpin on tap event? Thanks.
Which kind of index are you referring to? The index of the element in the list?
Yes, that’s right.
You can subscribe to the Clicked event of the Pushpin class in XAML and then, thanks to the sender parameter, get a reference to the Pushpin that has been selected. By accessing to the DataContext property, you’ll be able to get all the information about the object that you’re displaying on the map (for example, Restaurant in the sample code of the post)
Hello qmatteoq..
this post is very useful for me,.. many thanks 🙂
And i have a question, how to show our position immediately when the application is opened? as in the tutorial without having to press the “center”? Thanks.
Hi, just call the methods to Geolocalize the user in the Loaded event on the page, that is triggered when the page is loaded.
Thanks for response….
i have other question… in this your post, how to change default image pushpin when application launch with custom image i created?
Thanks
You can use the MapOverlay class, which can be used to add an arbitrary UIElement to the map. See here http://msdn.microsoft.com/library/windows/apps/jj207037(v=vs.105).aspx for more details.
Hi, I’ve installed the Toolkit via NuGet and added its reference at the beginning of the XAML but, apparently, it doesn’t recognize the MapExtensions element, do you have any idea?
Thank you, great blog by the way.
Hi! Is it a Windows Phone 8.0 app? Weird, it should work.
Yes, it’s a Windows Phone 8 app..this is crazy, it seems that I can’t do anytihing with the map..
Weird, can you send me a sample project to reproduce the issue?
Update Nuget, should work after.
Hi there,
Is there some way of removing pins, or relocating pins once I have placed them? I have tried changing my collection, however it doesn’t seem to be updating the map!
Thanks in advance,
Are you using an ObservableCollection to store your pushpins?
Hi, I have tried to use parts of your code but it doesn’t compile because in this line
“ObservableCollection children = MapExtensions.GetChildren(myMap);”
there is a type mismatch.
Error 1 Cannot implicitly convert type ‘System.Windows.DependencyObjectCollection’ to ‘System.Collections.ObjectModel.ObservableCollection’
Your full sample however does compile and run.
Most probably there has been an update between your time of writing and now
and the type of MapExtensions.GetChildren(Map element)
I just wanted to let you know
Thanks for the info, I’ll check!