WPF-бинг-карты управляют полилиниями/многоугольниками, которые не набираются на первое дополнение к коллекции

Я работаю над этим наземным проектом, где у нас есть элемент управления бинг-картами и где мы хотели бы рисовать полилинии на карте, используя привязку данных.

Странное поведение, которое происходит, заключается в том, что когда я нажимаю кнопку "Добавить", на карте ничего не происходит. Если немного переместить карту, полилиния будет отображена на карте. Другой сценарий такого рода работ, щелкнуть кнопку добавления один раз, ничего не происходит, щелкните его снова, и оба полилинии нарисованы. (В моей коллекции руководства у меня есть 4 LocationCollections), так что это происходит и для третьего щелчка и четвертого щелчка, где снова отображаются две линии.

Я понятия не имею, где искать больше, чтобы исправить это. Я попытался подписаться на события Layoutupdated, которые происходят в обоих случаях. Также добавлено событие, связанное с коллекцией, в наблюдаемое объединение, чтобы увидеть, активировано ли добавление, и да, это вызвано. Еще одна вещь, которую я пробовал, - это изменение полилинии на pushpin и выбор первого места из коллекции местоположений в структуре pipeview, чем ожидалось.

Я загрузил образец проекта, если вы хотите увидеть, что происходит.

Надеюсь, что кто-то может указать мне в правильном направлении, потому что у меня больше нет подсказки.

Ниже вы найдете код, который я написал:

У меня есть следующие режимы просмотра:

MainViewModel

public class MainViewModel
{
    private ObservableCollection<PipelineViewModel> _pipelines;

    public ObservableCollection<PipelineViewModel> Pipes
    {
        get { return _pipelines; }
    }

    public MainViewModel()
    {
        _pipelines = new ObservableCollection<PipelineViewModel>();
    }
}

И PipelineViewModel, который имеет коллекцию Locations, которая реализует 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");
        }
    }
}

Мой XAML выглядит следующим образом:

<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>

И в нашем коде мы настраиваем привязку и событие click следующим образом:

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;
}

Ответы

Ответ 1

Я немного искал эту проблему и обнаружил, что в реализации Map есть ошибка. Я также сделал обходной путь для него, который можно использовать как это

<m:Map ...>
    <m:MapItemsControl Name="x"
                       behaviors:MapFixBehavior.FixUpdate="True"/>
</m:Map>

Я включил это исправление в ваше примерное приложение и загрузил его здесь: SurfaceApplication3.zip


Визуальное дерево для каждого ContentPresenter выглядит следующим образом:

enter image description here

Когда вы добавляете новый элемент в коллекцию, Polygon изначально ошибается Points. Вместо таких значений, как 59, 29, он получает что-то вроде 0.0009, 0.00044.

Точки вычисляются в MeasureOverride в MapShapeBase, а часть, которая делает расчет, выглядит так:

MapMath.TryLocationToViewportPoint(ref this._NormalizedMercatorToViewport, location, out point2);

Изначально _NormalizedMercatorToViewport будет иметь свои значения по умолчанию (все установлено на 0), поэтому вычисления идут не так. _NormalizedMercatorToViewport устанавливается в методе SetView, который вызывается из MeasureOverride в MapLayer.

MeasureOverride в MapLayer имеет следующие два оператора if.

if ((element is ContentPresenter) && (VisualTreeHelper.GetChildrenCount(element) > 0))
{
    child.SetView(...)
}

Это выглядит как false, потому что ContentPresenter еще не имеет визуального дочернего элемента, он все еще сгенерирован. Это проблема.

Второй выглядит так:

IProjectable projectable2 = element as IProjectable;
if (projectable2 != null)
{
    projectable2.SetView(...);
}

Это выводится как false, так как элемент ContentPresenter не реализует IProjectable. Это реализуется дочерним элементом MapShapeBase, и еще раз этот ребенок еще не создан.

Итак, SetView никогда не будет вызван, а _NormalizedMercatorToViewport в MapShapeBase будет иметь свои значения по умолчанию, и вычисления будут ошибочными в первый раз при добавлении нового элемента.


Обход

Чтобы обойти эту проблему, нам нужно заставить повторить измерение MapLayer. Это необходимо сделать, когда новый ContentPresenter добавлен в MapItemsControl, но после того, как ContentPresenter имеет визуальный дочерний элемент.

Одним из способов принудительного обновления является создание вложенного свойства, в котором флаги метаданных AffectsRender, AffectsArrange и AffectsMeasure установлены в true. Затем мы просто изменяем значение этого свойства каждый раз, когда хотим сделать обновление.

Вот это приложенное поведение, которое делает это. Используйте его так:

<m:Map ...>
    <m:MapItemsControl Name="x"
                       behaviors:MapFixBehavior.FixUpdate="True"/>
</m:Map>

MapFixBehavior

public class MapFixBehavior
{
    public static DependencyProperty FixUpdateProperty =
        DependencyProperty.RegisterAttached("FixUpdate",
                                            typeof(bool),
                                            typeof(MapFixBehavior),
                                            new FrameworkPropertyMetadata(false,
                                                                          OnFixUpdateChanged));

    public static bool GetFixUpdate(DependencyObject mapItemsControl)
    {
        return (bool)mapItemsControl.GetValue(FixUpdateProperty);
    }
    public static void SetFixUpdate(DependencyObject mapItemsControl, bool value)
    {
        mapItemsControl.SetValue(FixUpdateProperty, value);
    }

    private static void OnFixUpdateChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
        MapItemsControl mapItemsControl = target as MapItemsControl;
        ItemsChangedEventHandler itemsChangedEventHandler = null;
        itemsChangedEventHandler = (object sender, ItemsChangedEventArgs ea) =>
        {
            if (ea.Action == NotifyCollectionChangedAction.Add)
            {
                EventHandler statusChanged = null;
                statusChanged = new EventHandler(delegate
                {
                    if (mapItemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
                    {
                        mapItemsControl.ItemContainerGenerator.StatusChanged -= statusChanged;
                        int index = ea.Position.Index + ea.Position.Offset;
                        ContentPresenter contentPresenter =
                            mapItemsControl.ItemContainerGenerator.ContainerFromIndex(index) as ContentPresenter;
                        if (VisualTreeHelper.GetChildrenCount(contentPresenter) == 1)
                        {
                            MapLayer mapLayer = GetVisualParent<MapLayer>(mapItemsControl);
                            mapLayer.ForceMeasure();
                        }
                        else
                        {
                            EventHandler layoutUpdated = null;
                            layoutUpdated = new EventHandler(delegate
                            {
                                if (VisualTreeHelper.GetChildrenCount(contentPresenter) == 1)
                                {
                                    contentPresenter.LayoutUpdated -= layoutUpdated;
                                    MapLayer mapLayer = GetVisualParent<MapLayer>(mapItemsControl);
                                    mapLayer.ForceMeasure();
                                }
                            });
                            contentPresenter.LayoutUpdated += layoutUpdated;
                        }
                    }
                });
                mapItemsControl.ItemContainerGenerator.StatusChanged += statusChanged;
            }
        };
        mapItemsControl.ItemContainerGenerator.ItemsChanged += itemsChangedEventHandler;
    }

    private static T GetVisualParent<T>(object childObject) where T : Visual
    {
        DependencyObject child = childObject as DependencyObject;
        while ((child != null) && !(child is T))
        {
            child = VisualTreeHelper.GetParent(child);
        }
        return child as T;
    }
}

MapLayerExtensions

public static class MapLayerExtensions
{
    private static DependencyProperty ForceMeasureProperty =
        DependencyProperty.RegisterAttached("ForceMeasure",
                                            typeof(int),
                                            typeof(MapLayerExtensions),
                                            new FrameworkPropertyMetadata(0,
                                                FrameworkPropertyMetadataOptions.AffectsRender |
                                                FrameworkPropertyMetadataOptions.AffectsArrange |
                                                FrameworkPropertyMetadataOptions.AffectsMeasure));

    private static int GetForceMeasure(DependencyObject mapLayer)
    {
        return (int)mapLayer.GetValue(ForceMeasureProperty);
    }
    private static void SetForceMeasure(DependencyObject mapLayer, int value)
    {
        mapLayer.SetValue(ForceMeasureProperty, value);
    }

    public static void ForceMeasure(this MapLayer mapLayer)
    {
        SetForceMeasure(mapLayer, GetForceMeasure(mapLayer) + 1);
    }
}