My question is about using bing maps with windows phone 7. Here is a summary of what I need to do
if this is the first time the service is polled
plot these coordinates as custom pushpins on the map (I am using Image and MapLayer)
PinObject pin = new PinObject() //Custom object
{
Id = groupMember.Id,
PushPin = new Image()
{
Source = new System.Windows.Media.Imaging.BitmapImage(new Uri("blackpin.png", UriKind.Relative)),
Opacity = 0.8,
Stretch = System.Windows.Media.Stretch.None
},
PinLocation = new GeoCoordinate(groupMember.Latitude, groupMember.Longitude)
};
imageLayer.AddChild(pin.PushPin, pin.PinLocation); //Initialized in constructor
pinObjects.Add(pin);// Add pin object to a list to provide a handle to the objects
auto set the map zoomlevel so that all the plotted points are visible (I would assume using a LocationRect.CreateLocationRect should do)
var coords = pinObjects.Select(p => p.PinLocation).ToList();
myMap.SetView(LocationRect.CreateLocationRect(coords));
else based on the new coordinates obtained, update the locations of each of the pushpins on the map PinObject pObj = pinObjects.FirstOrDefault(p => p.Id == groupMember.Id);
MapLayer.SetPosition(pObj.PushPin, new GeoCoordinate(groupMember.Latitude, groupMember.Longitude));
The pins load fiine and the call to the service to get their new locations loads fine as well, the problem is that their location on the map is never updated so basically they just sit still even though all this work is going on in the background, I have debugged so I know it works. How do I reset the location of the pins, if using an Image won't do, can I use a Pushpin object? How would this work?
Thanks in advance.
I've found that the best way to ensure the pushpins get updated is to call call SetView()
on the map again. You can pass in the existing view to basically force a refresh. Eg; MyMapControl.SetView(MyMapControl.BoundingRectangle);
MapItemsControl
ItemsSource
property with Pushpin
items created in the DataTemplate
. The push pins bind a GeoCoordinate
property to their Location
property. After an update of the property I call SetView()
as mentioned in my answer and it all works fine. Can you try posting your XAML - MrMDavidson 2012-04-08 03:06
Here is an option similar to Dispatcher.BeginInvoke but it works better for me in some cases. When I really need to get off the current thread with some work I will use a private static class UICallbackTimer to offset execution just a slight amount. (typos and untested code, just pulling out pieces here you'll have to debug in your code)
UICallbackTimer is not my code but it's available on the Internet. You can get information on this class by searching "private static class UICallbackTimer"
Here is the code to execute it.
UICallbackTimer.DelayExecution(TimeSpan.FromSeconds(.01),
() => (
MapLayer.SetPosition(pObj.PushPin, new GeoCoordinate(groupMember.Latitude, groupMember.Longitude))
);
and here is the class ( I place it inside the current object, so that it remains private to my class) Add using statement for System.Threading
private static class UICallbackTimer
{
private static bool _running = false;
private static int runawayCounter = 0;
public static bool running()
{
if (_running && runawayCounter++ < 10)
return _running;
runawayCounter = 0;
_running = false;
return _running;
}
public static void DelayExecution(TimeSpan delay, Action action)
{
_running = true;
System.Threading.Timer timer = null;
SynchronizationContext context = SynchronizationContext.Current;
timer = new System.Threading.Timer(
(ignore) =>
{
timer.Dispose();
_running = false;
context.Post(ignore2 => action(), null);
}, null, delay, TimeSpan.FromMilliseconds(-1));
}
}
Great Question!
Here is some realllly ugly code, but at least its working and something to start from.
I got the main structure from here. I'd appreciate it if someone could post an answer with proper binding and less code behind.
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--RESOURCES-->
<Grid.Resources>
<DataTemplate x:Key="LogoTemplate">
<maps:Pushpin Location="{Binding PinLocation}" />
</DataTemplate>
<maps:MapItemsControl x:Name="GroupAPins"
ItemTemplate="{StaticResource LogoTemplate}"
ItemsSource="{Binding PinsA}">
</maps:MapItemsControl>
</Grid.Resources>
<Grid x:Name="ContentPanel" Grid.Row="0" Margin="12,0,12,0"/>
</Grid>
public partial class MapPage : PhoneApplicationPage
{
private ObservableCollection<PinData> _pinsA = new ObservableCollection<PinData>();
private Map MyMap;
public ObservableCollection<PinData> PinsA { get { return this._pinsA; } }
public MapPage()
{
InitializeComponent();
this.DataContext = this;
//Create a map.
MyMap = new Map();
MyMap.CredentialsProvider = new ApplicationIdCredentialsProvider("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
//Remove the List of items from the resource and add it to the map
this.LayoutRoot.Resources.Remove("GroupAPins");
MyMap.Children.Add(GroupAPins);
MyMap.Center = new GeoCoordinate(40.74569634433956, -73.96717071533204);
MyMap.ZoomLevel = 5;
//Add the map to the content panel.
ContentPanel.Children.Add(MyMap);
loadAPins_fromString();
}
//ADD PIN TO COLLECTION
private void addPin(String lat, String lon)
{
PinData tmpPin;
tmpPin = new PinData()
{
PinLocation = new GeoCoordinate(System.Convert.ToDouble(lat), System.Convert.ToDouble(lon))
};
_pinsA.Add(tmpPin);
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += delegate(object sender, EventArgs args)
{
PinsA.Remove(tmpPin);
tmpPin.PinLocation.Latitude += 1;
PinsA.Add(tmpPin);
};
timer.Start();
}
//LOAD PINS ONE BY ONE
private string loadAPins_fromString()
{
//BAD
addPin("42.35960626034072", "-71.09212160110473");
//addPin("51.388066116760086", "30.098590850830067");
//addPin("48.17972265679143", "11.54910385608672");
addPin("40.28802528051879", "-76.65668606758117");
var coords = PinsA.Select(p => p.PinLocation).ToList();
MyMap.SetView(LocationRect.CreateLocationRect(coords));
return "A PINS LOADED - STRING";
}
}
public class PinData
{
public GeoCoordinate PinLocation{get;set;}
}