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

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

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

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

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

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

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

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

Модель главного представления

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>

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

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

person ChristiaanV    schedule 08.06.2012    source источник
comment
Я не могу помочь вам в этом, но протестировал ваш пример проекта; сделал кое-что методом проб и ошибок (недействительное, принудительное перемещение карты), но также не знаю, почему это не работает. Все, что вы реализовали, выглядит нормально. Но вот некоторые выводы: Добавление MapPolyline из CodeBehind работает нормально. Если вы используете другой элемент, такой как Pushpin, он также работает нормально. Таким образом, проблема связана со всем, что наследуется от MapShapeBase. И это MapPolyline и MapPolygon. Я просмотрел его через Reflector и попытался сравнить реализацию Pushpin с реализацией MapPolyline.   -  person SvenG    schedule 11.06.2012
comment
Я не могу тратить больше времени, но если бы я мог, я бы отладил отраженный код и посмотрел, почему Pushpin обновляется правильно, а MapPolyLine/MapPolygon — нет.   -  person SvenG    schedule 11.06.2012
comment
Привет SvenG, Спасибо, что уделили время просмотру. Да, я видел, что канцелярские кнопки работают нормально. Я также вызвал метод UpdateLayout() в MapItemsControl, а затем добавил к слою пустой UIElement, и он покажет полилинию. До сих пор не понимаю, почему это не работает :(   -  person ChristiaanV    schedule 12.06.2012
comment
@Clemens Извините, мне нужно использовать элемент управления картами Bing.   -  person ChristiaanV    schedule 14.06.2012


Ответы (1)


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

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

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


Визуальное дерево для каждого ContentPresenter выглядит так

введите здесь описание изображения

Когда вы добавляете новый элемент в коллекцию, 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>

Поведение MapFix

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

Расширения слоя карты

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);
    }
}
person Fredrik Hedblad    schedule 17.06.2012
comment
@ChristiaanV: Вам повезло с предложенным мной обходным путем? Если у вас есть какие-либо вопросы относительно ошибки, я буду рад попытаться ответить. Насколько я понимаю, проблема не в вашей реализации, а в реализации Map от Microsoft. Я думаю, что это проблема, которую необходимо решить с их стороны, и единственный способ заставить ее работать до тех пор, пока они этого не сделают, — это использовать какой-то обходной путь. - person Fredrik Hedblad; 17.06.2012
comment
Вау, это отличный ответ! Очень доволен этим! - person ChristiaanV; 18.06.2012
comment
Я искал решение этой проблемы в течение некоторого времени, и хотя были разные предложения, это единственное, которое (пока) работало надежно. Одна вещь, хотя; возможно, я неправильно структурирую свой XAML, но если вы поместите MapItemsControl внутри MapLayer, исправление не сработает. К счастью, мне не нужно много слоев для того, что я делаю. - person Martin Robins; 05.12.2013