Bing maps data bound pushpins fade-in animation

When you use Bing Maps and place pushpins on it, you have two basic options. One is to fetch all the pushpins (actually, locations) from some service and then programmatically add them to the map, or you can bind all the pins (locations) through MapItemsControl with ObservableCollection. In this article, I’ll show you how to add pins from the background worker in case you’re fetching a lot of them at the same time and you’re using data binding, and also how to create a smooth animation to make the pins fade-in as they become available.

I will continue with one of my last articles where I explained how to create a sort of a tooltip for pushpins, when you tap on them. In case you missed that one, here it is:

http://igrali.wordpress.com/2012/01/07/show-a-tooltip-for-tapped-pushpin-on-windows-phone/

If you would like the whole project, find it here:

https://skydrive.live.com/redir.aspx?cid=c8b1f62c5b2dc515&resid=C8B1F62C5B2DC515!260&parid=C8B1F62C5B2DC515!259&authkey=!AA0equ7I9MM6bi0

Now, instead of simply adding all the pins to the map, I’ll simulate a long background worker that gets them from a web service by simply putting the thread to sleep for 2000 milliseconds. Unfortunately, we can add the items to ObservableCollection in a thread safe manner so it’s necessary that we either use a simple List and then copy all the items to ObservableCollection (which would defeat the purpose of it), or we can report progress from the BackgroundWorker and then simply add the pushpins from there. Here’s what I mean. We change the PushpinViewModel.cs from the last article to look like this:

using System;  
using System.Collections.ObjectModel;  
using System.ComponentModel;  
using System.Device.Location;  
using PushpinTooltipSample.Models;

namespace PushpinTooltipSample.ViewModels  
{
    public class PushpinViewModel
    {
      private ObservableCollection<PushpinModel> _pushpins = 
                                   new ObservableCollection<PushpinModel>();

      private BackgroundWorker worker;

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

      public PushpinViewModel()
        {
            worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            worker.ProgressChanged += 
                new ProgressChangedEventHandler(worker_ProgressChanged);
            worker.WorkerReportsProgress = true;
            worker.RunWorkerAsync();
        }

      void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            PushpinModel ppModel = e.UserState as PushpinModel;
            _pushpins.Add(ppModel);
        }

      void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            var pp1 = new PushpinModel
            {
                Description = "Popular tourist destination in Croatia",
                Name = "Dubrovnik",
                Location = new GeoCoordinate(42.642749786377,
                                                18.1106491088867),
                DatetimeAdded = DateTime.Now
            };

            System.Threading.Thread.Sleep(2000);
            worker.ReportProgress(25, pp1);

            var pp2 = new PushpinModel
            {
                Description = "Diocletian's Palace is in this city",
                Name = "Split",
                Location = new GeoCoordinate(43.5069808959961,
                                                16.4417095184326),
                DatetimeAdded = DateTime.Now.AddMonths(-10)
            };

            System.Threading.Thread.Sleep(2000);
            worker.ReportProgress(50, pp2);

            var pp3 = new PushpinModel
            {
                Description = "Capital of Croatia",
                Name = "Zagreb",
                Location = new GeoCoordinate(45.807258605957,
                                                15.9675998687744),
                DatetimeAdded = DateTime.Now.AddYears(-1)
            };

            System.Threading.Thread.Sleep(2000);
            worker.ReportProgress(75, pp3);

            var pp4 = new PushpinModel
            {
                Description = "Popular for Tvrda, Old Town of the city",
                Name = "Osijek",
                Location = new GeoCoordinate(45.5584487915039,
                                                18.6764907836914),
                DatetimeAdded = DateTime.Now.AddDays(-10)
            };

            System.Threading.Thread.Sleep(2000);
            worker.ReportProgress(100, pp4);
        }
    }
}

The simulation is rather simple. We use the BackgroundWorker like we might in case we used a real service here, and every time we got a new pushpin, we reported progress to get back to the main thread and add the pushpin to the ObservableCollection. We simply sent the pushpin as the parameter of the ReportProgress method, casted it in the ProgressChanged event handler and added to the ObservableCollection. If you tested the code now, you would notice that it displays a new pin every 2 seconds, but it does it ugly with no animation. Let’s add it ourselves!

Add fade-in animation to Pushpins

First of all, change the MainPage.xaml.cs file to look like this:

using System.Linq;  
using System.Windows;  
using Microsoft.Phone.Controls;  
using Microsoft.Phone.Controls.Maps;  
using PushpinTooltipSample.ViewModels;  
using System.Device.Location;

namespace PushpinTooltipSample  
{
    public partial class MainPage : PhoneApplicationPage
    {
        // Constructor

        PushpinViewModel _viewModel;

        public MainPage()
        {
            _viewModel = new PushpinViewModel();
            InitializeComponent();
        }

        private void Map_Loaded(object sender, RoutedEventArgs e)
        {
            mapPins.ItemsSource = _viewModel.Pushpins;
            Mapa.Center = new GeoCoordinate(45.807258605957,
                                                15.9675998687744);
            Mapa.ZoomLevel = 6;
        }

        private void Pushpin_Tap(object sender, 
            System.Windows.Input.GestureEventArgs e)
        {
            var _ppmodel = sender as Pushpin;
            ContextMenu contextMenu = 
                ContextMenuService.GetContextMenu(_ppmodel);
            contextMenu.DataContext = _viewModel.Pushpins.Where
                (c => (c.Location 
                    == _ppmodel.Location)).FirstOrDefault();
            if (contextMenu.Parent == null)
            {
                contextMenu.IsOpen = true;
            }
        }
    }
}

The difference is that we now zoomed in to Croatia to make sure the pushpins are clear and visible immediately after running the app. In the MainPage.xaml, inside the MapItemsControl pushpin datatemplate, add the following animations that changes the Opacity from default 0 to 1 triggered by the pushpin getting loaded:

<my:Pushpin.Triggers>  
    <EventTrigger RoutedEvent="Canvas.Loaded">
        <BeginStoryboard>
            <Storyboard>
                <DoubleAnimation Duration="0:0:1" To="1" 
                                 Storyboard.TargetProperty="Canvas.Opacity" 
                                 Storyboard.TargetName="PointMe" />
            </Storyboard>
        </BeginStoryboard>
    </EventTrigger>
</my:Pushpin.Triggers>

So the whole MapItemsControl datatemplate now looks like this:

<DataTemplate>  
<my:Pushpin
    Background="Blue"
    Location="{Binding Location}" Tap="Pushpin_Tap" Name="PointMe" 
    Opacity="0">
<my:Pushpin.Triggers>
    <EventTrigger RoutedEvent="Canvas.Loaded">
        <BeginStoryboard>
            <Storyboard>
                <DoubleAnimation Duration="0:0:1" To="1" 
                                 Storyboard.TargetProperty="Canvas.Opacity" 
                                 Storyboard.TargetName="PointMe" />
            </Storyboard>
        </BeginStoryboard>
    </EventTrigger>
</my:Pushpin.Triggers>
       <sltkit:ContextMenuService.ContextMenu>
        <sltkit:ContextMenu IsZoomEnabled="False" 
                Style="{StaticResource MenuStyle}">
            <sltkit:MenuItem 
                Style="{StaticResource MenuItemStyle}"/>
        </sltkit:ContextMenu>
    </sltkit:ContextMenuService.ContextMenu>
</my:Pushpin>
</DataTemplate>

Now when you run the app, every pin that’s added to the ObservableCollection binded to the Map items source now fades-in slowly to make it more natural.

I hope this helps you make a better data bound map in your app.

fadein

Questions? Twitter! Or leave a comment!

comments powered by Disqus