1

I have a UWP application with a MapControl. There are a list of points that get displayed as pins on the map. When an item is clicked/tapped details appear next to the pin. However it seems like binding to location doesn't work properly. The details control often doesn't display until the item is clicked a second time or the map is moved/zoomed/changed in some way. When I'm debugging I can see the location gets set correctly on the first click and doesn't get changed again on the second click, so it appears to just be a display/refresh issue.

I have created a little demo app that shows the behaviour here. If you click on any of the map pins, a text block should appear beneath it with a name. It (usually) works on the first click but if you then select a different pin the text will (probably) change but the location will not. If you click on that different pin a second time the text block will move to it correctly. This doesn't always happen but from my testing it seems to happen about 90% of the time. I have also seen another issue where the text block moves to the correct pin but is initially not aligned correctly, and again once you click again it moves to the correct location.

Am I doing something wrong here? Has anyone encountered this and know how to work around it? Is this a bug in the map control and if so where is the best place to report these things (as much as Microsoft have put on Github, I can't find this anywhere).

rcarrington
  • 1,485
  • 2
  • 12
  • 23

1 Answers1

1

UWP MapControl.Location binding not working correctly

I could reproduce your issue, and when you clicked the map icon then zoom the map, the location will work properly. The reason is that the map control does not refresh after map icon selected. I think that create new Grid and bind with map location out DataTemplate cause this issue. The better way is make a IsVisibility for Thing model and process all data binding in the DataTemplate. For detail steps please refer the following code.

public class MainViewModel : BaseViewModel
{
    public MainViewModel()
    {
        Things = new ObservableCollection<Thing>
        {
            new Thing("One World Trade Center", 40.712903, -74.013203, SelectMe),
            new Thing("Carlton Centre", -26.205556, 28.046667, SelectMe),
            new Thing("Q1", -28.006111, 153.429444, SelectMe),
            new Thing("Gran Torre Santiago", -33.416944, -70.606667, SelectMe),
            new Thing("Burj Khalifa", 25.197139, 55.274111, SelectMe),
            new Thing("Lakhta Center", 59.987139, 30.177028, SelectMe),
            new Thing("Long Duration Balloon Payload Preparation Buildings", -77.846323, 166.668235, SelectMe),
        };
    }

    public ObservableCollection<Thing> Things { get; }


    private Thing previousThing;
    private void SelectMe(Thing thing)
    {
        if (previousThing != null) previousThing.IsVisibility = false;
        thing.IsVisibility = true;
        previousThing = thing;
    }
}

public class Thing : BaseViewModel
{
    private bool _isVisibility;

    public Thing(string name, double latitude, double longitude, Action<Thing> selector)
    {
        Name = name;
        Location = new Geopoint(new BasicGeoposition { Latitude = latitude, Longitude = longitude });
        SelectMeCommand = new RelayCommand(() => selector(this));
    }

    public string Name { get; set; }
    public Geopoint Location { get; set; }
    public ICommand SelectMeCommand { get; }
    public bool IsVisibility { get => _isVisibility; set => SetProperty(ref _isVisibility, value); }
}

public class VisibleWhenNotNullConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        return (bool)value == true ? Visibility.Visible : Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotSupportedException();
    }
}

Xaml

<map:MapControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
    <map:MapItemsControl ItemsSource="{x:Bind ViewModel.Things}">
        <map:MapItemsControl.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Canvas map:MapControl.Location="{Binding Location}">
                        <Path
                            Margin="0"
                            Data="M14-32h-28v27h8l6 5 6-5h8z"
                            Fill="HotPink"
                            IsHitTestVisible="True"
                            Stroke="Black"
                            StrokeThickness="2"
                            >

                            <interactivity:Interaction.Behaviors>
                                <core:EventTriggerBehavior EventName="Tapped">
                                    <core:InvokeCommandAction Command="{Binding SelectMeCommand, Mode=OneWay}" />
                                </core:EventTriggerBehavior>
                            </interactivity:Interaction.Behaviors>
                        </Path>
                        <TextBlock Text="{Binding Name, Mode=OneWay}" 
                                   Visibility="{Binding IsVisibility, Converter={StaticResource VisibleWhenNotNull}, Mode=OneWay}" />
                    </Canvas>
                </Grid>
            </DataTemplate>
        </map:MapItemsControl.ItemTemplate>
    </map:MapItemsControl>
</map:MapControl>
Nico Zhu
  • 32,367
  • 2
  • 15
  • 36
  • My example has very few data points and the selected control is very simple. In the real app there are a lot more data points and much more complicated control when a pin is selected. Surely this will be a lot less performant? Obviously I can test this for our particular case but this feels like we're working around a bug. This feels like it should be documented (that you can't bind to MapControl.Location) but even then I feel like there are legitimate cases where you might need to do this. – rcarrington May 21 '19 at 17:11
  • You could bind to `MapControl.Location`, but it's better used in the `DataTemplate`. – Nico Zhu May 22 '19 at 03:01