Exercise 2: Handling and Customizing Pushpins

In this exercise you will:

  • Learn how to use map layers with data binding
  • Create a pushpin layer
  • Add a pushpin for indicating current location
  • Handle pushpin clicks
  • Add pushpins from a catalog to the map
  • Customize the pushpin look and feel

Task 1 – Creating a Pushpins Layer

In this task you will learn how to create a map layer and how to populate it with pushpin data using data binding. A map layer is represented by a custom panel of type Microsoft.Phone.Controls.Maps.MapLayer. Using the MapLayer panel you can have any kind of UIElement on top of the map. The MapLayer panel knows how to lay out elements on the map using geo coordinates. A map control can have zero or more layers.

Although we can add and remove visual items directly to the MapLayer panel from code behind, we will populate map layers using the data binding mechanism. Instead of using MapLayer directly, we will use another control of type Microsoft.Phone.Controls.Maps.MapItemsControl. This custom ItemsControl uses the MapLayer panel to layout items by default.

In this task you will create also a pushpin catalog and display it using a popup window. To build a catalog, you will bind a list box to a collection of predefined pushpin models. Selecting a pushpin from the catalog will clone it and add that pushpin to the touched area. You will use the Map control APIs to translate a view point to geographic location.

  1. Open the starter solution located in the Source\Ex2-HandlingPushpins\Begin folder.

    Note:
    You should set the Bing Maps application Id in the App class with your private key created in the previous exercise.

  2. Open the file named MainPage.xaml.cs.
  3. To populate the pushpins layer with pushpins, add a new private read-only field called _pushpins of type ObservableCollection<PushpinModel>, and initialize it with one PushpinModel instance using the default location.

    C#

    private readonly ObservableCollection<PushpinModel> _pushpins = new ObservableCollection<PushpinModel> { new PushpinModel { Location = DefaultLocation } };

  4. Expose the _pushpins field with public property so you can bind it with the pushpins layer.

    C#

    public ObservableCollection<PushpinModel> Pushpins { get { return _pushpins; } }

  5. Inside the DefaultStyle.xaml resource dictionary, create a new style for the pushpin as follows:

    XAML

    <Style x:Key="PushpinStyle" TargetType="m:Pushpin"> <Setter Property="BorderBrush" Value="#FFF4F4F5" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate> <Canvas Height="0" RenderTransformOrigin="0.5,0.5" Width="0"> <Canvas RenderTransformOrigin="0.5,0.5" Height="1" Width="1"> <Path Data="M22.5,1 C34.374123,1 44,10.625878 44,22.5 C44,31.034525 39.027256,38.407604 31.821138,41.879868 L31.359026,42.095631 L22.702744,60.864998 L13.900847,42.209641 L13.651964,42.100761 C6.1912994,38.727623 0.99999976,31.220058 1,22.5 C0.99999976,10.625878 10.625878,1 22.5,1 z" Fill="{TemplateBinding BorderBrush}" Height="61.865" Stretch="Fill" Stroke="Black" StrokeThickness="2" StrokeLineJoin="Miter" UseLayoutRounding="False" Width="45" RenderTransformOrigin="0.5,0.5" Canvas.Left="-2.703" Canvas.Top="-7.187" > <Path.RenderTransform> <CompositeTransform TranslateX="-20" TranslateY="-55"/> </Path.RenderTransform> </Path> <Path Data="M35,17.5 C35,27.164984 27.164984,35 17.5,35 C7.8350167,35 0,27.164984 0,17.5 C0,7.8350167 7.8350167,0 17.5,0 C27.164984,0 35,7.8350167 35,17.5 z" Fill="{TemplateBinding Background}" HorizontalAlignment="Left" Height="35" Stretch="Fill" StrokeThickness="2" VerticalAlignment="Top" RenderTransformOrigin="0.5,0.5" Width="35" UseLayoutRounding="False" Canvas.Top="-7.4" Canvas.Left="-2.888" > <Path.RenderTransform> <CompositeTransform TranslateX="-15" TranslateY="-50"/> </Path.RenderTransform> </Path> </Canvas> <ContentPresenter Width="35" Height="35" RenderTransformOrigin="0.5,0.5" Canvas.Top="-3.5"> <ContentPresenter.RenderTransform> <CompositeTransform TranslateX="-18" TranslateY="-54"/> </ContentPresenter.RenderTransform> </ContentPresenter> </Canvas> </ControlTemplate> </Setter.Value> </Setter> </Style>
    Note:
    If you design your own pushpin control template, you should know that the default relative point of a pushpin when scaling the map (zoom) is the bottom left corner, not the center or top left. You can easily create your own control template using Microsoft Expression Blend for Windows Phone, which is part of the Windows Phone development tools.

  6. Add new public property of type Uri, named Icon to the PushpinModel class. You will use it to bind the image of the pushpin.

    C#

    public class PushpinModel { public GeoCoordinate Location { get; set; } public Uri Icon { get; set; } ... }

  7. Open the MainPage.xaml file. Set the content of the pushpin to an Image element and bind the Image.Source property to the PushpinModel.Icon property.

    XAML

    <my:MapItemsControl ItemsSource="{Binding Pushpins}"> <my:MapItemsControl.ItemTemplate> <DataTemplate> <my:Pushpin Style="{StaticResource PushpinStyle}" Location="{Binding Location}" Background="{Binding TypeName, Converter={StaticResource PushpinTypeBrushConverter}}"> <Image Source="{Binding Icon}" /> </my:Pushpin> ...

  8. In the MainPage.xaml.cs, set the PushpinData.Icon property of pushpin instance you’ve created before with a relative Uri to the PushpinLocation.png icon file located in the Pushpins project folder.

    C#

    private readonly ObservableCollection<PushpinModel> _pushpins = new ObservableCollection<PushpinModel> { new PushpinModel { Location = DefaultLocation, Icon = new Uri("/Resources/Icons/Pushpins/PushpinLocation.png", UriKind.Relative) } };

  9. Open the file name PushpinCatalog.cs under Models folder and add the following highlighted code. This file defines a class which contains one pushpin instance per pushpin type.

    C#

    public class PushpinCatalog { ... public PushpinCatalog() { InitializePuspins(); } private void InitializePuspins() { string[] pushpinIcons = { "PushpinBicycle.png", "PushpinCar.png", "PushpinDrink.png", "PushpinFuel.png", "PushpinHouse.png", "PushpinRestaurant.png", "PushpinShop.png" }; var pushpins = from icon in pushpinIcons select new PushpinModel { Icon = new Uri("/Resources/Icons/Pushpins/" + icon, UriKind.Relative), TypeName = System.IO.Path.GetFileNameWithoutExtension(icon) }; _items = pushpins.ToList(); } }

  10. Open MainPage.xaml.cs file. To center the popup at the exact touch location, find the CenterPushpinsPopup method located in the MainPage.xaml.cs file, and add logic to calculate the popup’s Canvas.Top value based on the size of the list box and the touch point.

    C#

    private void CenterPushpinsPopup(Point touchPoint) { Canvas.SetTop(PushpinPopup, touchPoint.Y - ListBoxPushpinCatalog.Height / 2); }

  11. Now that the catalog is ready, let’s add logic to pick a pushpin and place it at the exact location. Register the ListBox.SelectionChanged event with the ListBoxPushpinCatalog_SelectionChanged event handler provided with the starter solution.
  12. Add a new method named Clone to the PushpinModel class. This method should return a clone of the PushpinModel instance with a different geo location provided as parameter of type GeoCoordinate.

    C#

    public PushpinModel Clone(GeoCoordinate location) { return new PushpinModel { Location = location, TypeName = TypeName, Icon = Icon }; }

  13. In the MainPage.xaml.cs, find the CreateNewPushpin method. This method is called by the ListBoxPushpinCatalog_SelectionChanged located in the MainPage.cs. It has two parameters: the selected pushpin model and the exact touch point in view coordinates.Translate the touch point to geo coordinates using the Map.TryViewportPointToLocation method.

    C#

    private void CreateNewPushpin(object selectedItem, Point point) { GeoCoordinate location; Map.TryViewportPointToLocation(point, out location); }

  14. Now that you’ve got the exact geo coordinate for the pushpin, clone the selected pushpin with the new coordinate, and add the clone to the Pushpins collection. This will generate a new pushpin on the correct geo location on the map.

    C#

    private void CreateNewPushpin(object selectedItem, Point point) { GeoCoordinate location; Map.TryViewportPointToLocation(point, out location); var pushpinPrototype = selectedItem as PushpinModel; var pushpin = pushpinPrototype.Clone(location); Pushpins.Add(pushpin); }

  15. Open the file DefaultStyles.xaml under Resources\Styles and add the following code as a resource to resource dictionary.

    XAML

    <ResourceDictionary ... xmlns:converters="clr-namespace:UsingBingMaps.Converters"> <converters:PushpinTypeBrushConverter x:Key="PushpinTypeBrushConverter" /> </ResourceDictionary>

  16. To center the map on a pushpin while touching it, register the Pushpin.MouseLeftButtonUp event in the MainPage.xaml file. Since the Pushpin is defined by a DataTemplate located directly inside MainPage.xaml, you can simply register the event handler over there. Otherwise you can use an Attached Behavior to activate a command. In the pushpin DataTemplate, register to the Pushpin.MouseLeftButtonUp event.
  17. Open the file name MainPage.xaml.cs and the following code. It handles the event by setting the MainPage.Center property with the pushpin’s Location property. You can extract the pushpin object from the event handler sender argument.

    C#

    private void Pushpin_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { var pushpin = sender as Pushpin; Center = pushpin.Location; }

  18. To test the results, press F5, go to the default location, click and hold a position on the map then select a pushpin from the list. The pushpin you’ve selected should be added to the map. Repeat this behavior for adding more pushpins. Click on any pushpin to center the map on it.

    Figure 11

    Pushpin selection

    This concludes the exercise.

    Note:
    The complete solution for this exercise is provided at the following location: Source\Ex2-HandlingPushpins\End.