4
votes

I am having great difficulty adding pushpins to a map that works in a way I like.

First, I tried adding MapItemsControl and binding to it.

<map:MapItemsControl ItemsSource="{Binding Path=Locations}">
    <map:MapItemsControl.ItemTemplate>
        <DataTemplate>
            <controls:NumberedCircle Number="{Binding Path=Number}" map:MapControl.Location="{Binding Path=Point}"/>      
        </DataTemplate>
    </map:MapItemsControl.ItemTemplate>
</map:MapItemsControl>

This works, but looks absolutely terrible when you pan the map; there is a VERY noticeable delay between the map panning and the pushpin icons catching up to the locations where they are supposed to be. If I pan the map back and forth fast enough, I can make a pushpin jump from one side of the street to the other. This happens with just one item in the list, so it's not like the map is being overwhelmed.

Here's a video of the effect: http://youtu.be/9FIvkOf71bg

Next I tried adding MapIcons to the MapElements collection. This was done in a Behavior since I'm using MVVM, but the general idea is

var mapIcon = new MapIcon() { Location = pushpinDrawBehavior.Location};
mapControl.MapElements.Add(mapIcon);

This works great. I pan the map, the MapIcon stays exactly where it should be no matter how fast I pan around. No laggy behavior at all. But -- the MapIcon appears and disappears when you zoom in and out, as explained in the documentation:

The MapIcon is not guaranteed to be shown. It may be hidden when it obscures other elements or labels on the map.

I'm very frustrated because I have two ways to add pushpins to a map, and they both create a horrible user experience for different reasons. So, questions are:

1) Am I missing anything? Is there a way to make XAML elements not lag on pan? Is there a way to make MapIcons always visible?

2) Is there a way to create a custom class that will behave like a MapIcon? It derives from MapElement, but I can't see how the actual rendering of the pushpin is done, or how the layout of the image, title, etc is defined. If I could create my own MapIcon, I could potentially disable the hiding behavior.

Edit: Ran the app on a Nokia 925 and the effect is much less noticeable (the above video is from a 521). It seems keeping the XAML overlays synced with the map is very processor intensive and will be a lot worse on lesser hardware.

2
Hi Paul, do you have anything new to add to this since? Noticing the same effect.James Mundy

2 Answers

2
votes

I'm afraid to tell you that there's not much you can do about that, besides decreasing the amount of elements or the complexity of the XAML code of the pushpins.

The difference between MapIcon and MapElements is that MapIcons are Bitmaps which are directly sent to the map control and are rendered there, while MapElements are XAML elements and rendered by the XAML thread. The delay is caused because of the sync that is required between the map interaction in the map thread and the UI thread of your application.

What you may try, is the XAML of the pushpins as a bitmap (see RenderTargetBitmap). The rendering may take some time, but then you can use them for MapIcons. However, I tried this some months ago and found no way to display the MapIcons all the time.

As I am also developing an app where I would like to have MapIcons, that are always visible, so let's hope that there will be changes to the Map component in the future. There are quite a lot of bugs in the map, e.g. I am also not able to change the anchor point of a push-pin... MS could improve a lot here.

Edit: to learn more about the map control and MapIcons/MapElements/..., have a look at this BUILD conference talk.

0
votes

This may be a long shot (and not too pretty), but try with MapItemsControl populated on the UI thread.

Create a class for pushpin:

public class PushpinData
{
    public GeoCoordinate Location { get; set; }
    public string Text { get; set; }
    public SolidColorBrush DisplayBrush { get; set; }
}

Create your MapControl with DataTemplate for pushpin:

xmlns:maps="clr-namespace:Microsoft.Phone.Maps.Controls;assembly=Microsoft.Phone.Maps"
xmlns:maptk="clr-namespace:Microsoft.Phone.Maps.Toolkit;assembly=Microsoft.Phone.Controls.Toolkit"

<maps:Map x:Name="mapControl" >
    <maptk:MapExtensions.Children>
    <maptk:MapItemsControl x:Name="PushpinLayer">
        <maptk:MapItemsControl.ItemTemplate>
            <DataTemplate>
                <maptk:Pushpin GeoCoordinate="{Binding Location}" 
                Content="{Binding Text}" 
                Background="{Binding DisplayBrush}"/>
            </DataTemplate>
        </maptk:MapItemsControl.ItemTemplate>
    </maptk:MapItemsControl>
    </maptk:MapExtensions.Children>
</maps:Map>

Declare your pushpin list in the View and in the Loaded event of the map control, get reference to the MapItemsControl and assign its ItemsSource:

using Microsoft.Phone.Maps.Toolkit;

ObservableCollection<PushpinData> pushpinList = new ObservableCollection<PushpinData>();

mapControl.Loaded += (s, e) =>
  {
    ObservableCollection<DependencyObject> children = MapExtensions.GetChildren(mapControl);
    var pushPinLayer = children.FirstOrDefault(x => x.GetType() == typeof(MapItemsControl)) as MapItemsControl;
    pushPinLayer.ItemsSource = pushpinList;
  }

Once you have that, you can populate the pushpinList. To avoid breaking MVVM, you can of course receive pushpins list from your VM via Messenger of sorts. Also make sure your pushpin collection contains only the ones which are displayed at any given time.