13 August 2014

Querying the Windows Phone 8.1 map when there are child objects over it

With the Windows Phone 8.1 SDK came (yet again) an improved Map Control. This brought, among other things, the MapIcon. In ye olde days, the map could only draw lines and polygons natively – when you had to draw icons you had to add child objects to the map itself, which were drawn by the UI thread and not by the map. This is slower and eats more resources.

imageThe new map still offers this possibility. And sometimes we need it, too, for a couple of reasons. First of all, a MapIcon can only display an image and an optional label. Second, MapIcons are drawn on the map using a ‘best effort’, which mean overlapping icons don’t get displayed at all and – worse – if you query the map, using the FindMapElementsAtOffset method, they are not found either.

So in some cases we just need to resort to drawing XAML elements by adding to the map’s Children collection – an option, which luckily has been improved tremendously as we now, for instance, can data bind these elements using the MapItemsControl, as explained by my smart fellow MVP Shawn Kendrot. Before 8.1, we needed the Windows Phone Toolkit for that.

But I noticed something very odd. I have been trained to use the MapTapped event to trap the location where the user tapped, as the Tapped event is being trapped by the map itself. It never fires. So I used MapTapped, and then FindMapElementsAtOffset to query the map;

But to my dismay I also discovered that the MapTapped event does not fire either when you tap on a location where a child object is displayed. Ergo – if you tap on the cross where the Windows Phone logo is displayed, nothing happens. So how am I to find out what’s underneath there?

After some trashing around I stumbled upon the answer – if you tap on a child element that’s on the map – not the MapTapped, but the Tapped event fires. “The event that never fires” comes to the rescue. In addition, the Tapped event on the child object itself fires. So I created the following method to query the map:

private void QueryMap(Point offset)
{
  foreach (var obj in MyMap.FindMapElementsAtOffset(offset))
  {
    Debug.WriteLine(obj.GetType());
  };
}
The regular method to trap MapTapped:
private void MyMap_OnMapTapped(MapControl sender, MapInputEventArgs args)
{
  Debug.WriteLine("MyMap.MapTapped occurred");
  QueryMap(args.Position);
}

And a method to trap Tapped when that occurs, and that also routes to the QueryMap method, with a little help from the GetPosition event method:

private void MyMap_OnTapped(object sender, TappedRoutedEventArgs e)
{
  Debug.WriteLine("MayMap.Tapped occurred");
  QueryMap(e.GetPosition(MyMap));
}

And because the events never fire simultaneously, you will see the map finds one PolyLine when you tap on a line just next to the symbol, will and it fill find two lines (the crossing of both) when you tap on the symbol- but never will you get a double hit from both events.

By attaching to the Tapped event of the Child object I can also detect if the symbol itself is tapped:

var childObj = new Image { 
  Source = new BitmapImage(new Uri("ms-appx:///Assets/wplogo.png")) };
MapControl.SetNormalizedAnchorPoint(childObj, new Point(0.5, 0.5));
MapControl.SetLocation(childObj, p5);
MyMap.Children.Add(childObj);
childObj.Tapped += ChildObj_Tapped;



void ChildObj_Tapped(object sender, TappedRoutedEventArgs e)
{
  Debug.WriteLine("ChildObj.Tapped occurred");
}

And thus we can find everything on the map, provided you attach the right methods to the right events. The MapTapped/Tapped combo of the map is an odd one but once you know how to do it – a few extra lines of code does it all.

A demo app demonstrating the whole concept can be found here.The only thing it does is write things to the debug console when you do something, so pay attention what happens there ;-)

No comments: