I’m working on this surface project where we have a bing maps control and where we would like to draw polylines on the map, by using databinding.
The strange behaviour that’s occuring is that when I click the Add button, nothing happens on the map. If I move the map little bit, the polyline is drawn on the map. Another scenario that kind of works, is click the add button once, nothing happens, click it again both polylines are drawn. (In my manual collection I have 4 LocationCollections) so the same happens for the 3rd click and the fourth click where again both lines are drawn.
I have totally no idea where to look anymore to fix this. I have tried subscribing to the Layoutupdated events, which occur in both cases. Also added a collectionchanged event to the observablecollection to see if the add is triggered, and yes it is triggered. Another thing I tried is changing the polyline to pushpin and take the first location from the collection of locations in the pipelineviewmodel, than it’s working a expected.
I have uploaded a sample project for if you want to see yourself what’s happening.
Really hope that someone can point me in the right direction, because i don’t have a clue anymore.
Below you find the code that i have written:
I have the following viewmodels:
MainViewModel
public class MainViewModel
{
private ObservableCollection<PipelineViewModel> _pipelines;
public ObservableCollection<PipelineViewModel> Pipes
{
get { return _pipelines; }
}
public MainViewModel()
{
_pipelines = new ObservableCollection<PipelineViewModel>();
}
}
And the PipelineViewModel which has the collection of Locations which implements INotifyPropertyChanged:
PipelineViewModel
public class PipelineViewModel : ViewModelBase
{
private LocationCollection _locations;
public string Geometry { get; set; }
public string Label { get; set; }
public LocationCollection Locations
{
get { return _locations; }
set
{
_locations = value;
RaisePropertyChanged("Locations");
}
}
}
My XAML looks like below:
<s:SurfaceWindow x:Class="SurfaceApplication3.SurfaceWindow1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="http://schemas.microsoft.com/surface/2008"
xmlns:m="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF"
Title="SurfaceApplication3">
<s:SurfaceWindow.Resources>
<DataTemplate x:Key="Poly">
<m:MapPolyline Locations="{Binding Locations}" Stroke="Black" StrokeThickness="5" />
</DataTemplate>
</s:SurfaceWindow.Resources>
<Grid>
<m:Map ZoomLevel="8" Center="52.332074,5.542302" Name="Map">
<m:MapItemsControl Name="x" ItemsSource="{Binding Pipes}" ItemTemplate="{StaticResource Poly}" />
</m:Map>
<Button Name="add" Width="100" Height="50" Content="Add" Click="add_Click"></Button>
</Grid>
</s:SurfaceWindow>
And in our codebehind we are setting up the binding and the click event like this:
private int _counter = 0;
private string[] geoLines;
private MainViewModel _mainViewModel = new MainViewModel();
/// <summary>
/// Default constructor.
/// </summary>
public SurfaceWindow1()
{
InitializeComponent();
// Add handlers for window availability events
AddWindowAvailabilityHandlers();
this.DataContext = _mainViewModel;
geoLines = new string[4]{ "52.588032,5.979309; 52.491143,6.020508; 52.397391,5.929871; 52.269838,5.957336; 52.224435,5.696411; 52.071065,5.740356",
"52.539614,4.902649; 52.429222,4.801025; 52.308479,4.86145; 52.246301,4.669189; 52.217704,4.836731; 52.313516,5.048218",
"51.840869,4.394531; 51.8731,4.866943; 51.99841,5.122375; 52.178985,5.438232; 51.8731,5.701904; 52.071065,6.421509",
"51.633362,4.111633; 51.923943,6.193542; 52.561325,5.28717; 52.561325,6.25946; 51.524125,5.427246; 51.937492,5.28717" };
}
private void add_Click(object sender, RoutedEventArgs e)
{
PipelineViewModel plv = new PipelineViewModel();
plv.Locations = AddLinestring(geoLines[_counter]);
plv.Geometry = geoLines[_counter];
_mainViewModel.Pipes.Add(plv);
_counter++;
}
private LocationCollection AddLinestring(string shapegeo)
{
LocationCollection shapeCollection = new LocationCollection();
string[] lines = Regex.Split(shapegeo, ";");
foreach (string line in lines)
{
string[] pts = Regex.Split(line, ",");
double lon = double.Parse(pts[1], new CultureInfo("en-GB"));
double lat = double.Parse(pts[0], new CultureInfo("en-GB"));
shapeCollection.Add(new Location(lat, lon));
}
return shapeCollection;
}
I did some digging on this problem and found that there is a bug in the
Mapimplementation. I also made a workaround for it which can be used like thisI included this fix in your sample application and uploaded it here: SurfaceApplication3.zip
The visual tree for each
ContentPresenterlooks like thisWhen you add a new item to the collection the
Polygongets the wrongPointsinitially. Instead of values like59, 29it gets something like0.0009, 0.00044.The points are calculated in
MeasureOverrideinMapShapeBaseand the part that does the calculation looks like thisInitially,
_NormalizedMercatorToViewportwill have its default values (everything is set to 0) so the calculations goes all wrong._NormalizedMercatorToViewportgets set in the methodSetViewwhich is called fromMeasureOverrideinMapLayer.MeasureOverrideinMapLayerhas the following two if statements.This comes out as
falsebecause theContentPresenterhasn’t got a visual child yet, it is still being generated. This is the problem.The second one looks like this
This comes out as
falseas well because the element, which is aContentPresenter, doesn’t implementIProjectable. This is implemented by the childMapShapeBaseand once again, this child hasn’t been generated yet.So,
SetViewnever gets called and_NormalizedMercatorToViewportinMapShapeBasewill have its default values and the calculations goes wrong the first time when you add a new item.Workaround
To workaround this problem we need to force a re-measure of the
MapLayer. This has to be done when a newContentPresenteris added to theMapItemsControlbut after theContentPresenterhas a visual child.One way to force an update is to create an attached property which has the metadata-flags
AffectsRender,AffectsArrangeandAffectsMeasureset to true. Then we just change the value of this property everytime we want to do the update.Here is an attached behavior which does this. Use it like this
MapFixBehavior
MapLayerExtensions