简体   繁体   English

WPF bing贴图控制折线/多边形在第一次添加到集合时不绘制

[英]WPF bing maps control polylines/polygons not draw on first add to collection

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. 我正在研究这个表面项目,我们有一个bing贴图控件,我们希望通过数据绑定在地图上绘制折线。

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. (在我的手册集中,我有4个LocationCollections),因此第3次点击和第4次点击同样会发生两条线的绘制。

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. 我已经尝试订阅Layoutupdated事件,这两种情况都会发生。 Also added a collectionchanged event to the observablecollection to see if the add is triggered, and yes it is triggered. 还向observablecollection添加了一个collectionchanged事件,以查看是否触发了add,并且是触发了它。 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: 我有以下viewmodels:

MainViewModel 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具有实现INotifyPropertyChanged的Locations集合:

PipelineViewModel 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: 我的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>

And in our codebehind we are setting up the binding and the click event like this: 在我们的代码隐藏中,我们正在设置绑定和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;
}

I did some digging on this problem and found that there is a bug in the Map implementation. 我做了一些挖掘这个问题,发现Map实现中存在一个错误。 I also made a workaround for it which can be used like this 我也为它做了一个解决方法,可以像这样使用

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

I included this fix in your sample application and uploaded it here: SurfaceApplication3.zip 我在您的示例应用程序中包含了此修复程序并将其上载到此处: SurfaceApplication3.zip


The visual tree for each ContentPresenter looks like this 每个ContentPresenter的可视树看起来像这样

在此输入图像描述

When you add a new item to the collection the Polygon gets the wrong Points initially. 当您向集合中添加新项时, Polygon最初会获得错误的Points Instead of values like 59, 29 it gets something like 0.0009, 0.00044 . 取而代之的是像0.0009, 0.00044这样的值59, 29它会得到像0.0009, 0.00044

The points are calculated in MeasureOverride in MapShapeBase and the part that does the calculation looks like this 这些点在MapShapeBase中的MeasureOverride中计算,执行计算的部分如下所示

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

Initially, _NormalizedMercatorToViewport will have its default values (everything is set to 0) so the calculations goes all wrong. 最初, _NormalizedMercatorToViewport将具有其默认值(一切都设置为0),因此计算完全错误。 _NormalizedMercatorToViewport gets set in the method SetView which is called from MeasureOverride in MapLayer . _NormalizedMercatorToViewport在方法SetView设置,该方法在MapLayerMeasureOverride调用。

MeasureOverride in MapLayer has the following two if statements. MapLayer中的MeasureOverride具有以下两个if语句。

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

This comes out as false because the ContentPresenter hasn't got a visual child yet, it is still being generated. 这是false因为ContentPresenter还没有一个可视的孩子,它仍在生成。 This is the problem . 这是问题所在

The second one looks like this 第二个看起来像这样

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

This comes out as false as well because the element, which is a ContentPresenter , doesn't implement IProjectable . 这也是false ,因为作为ContentPresenter的元素不实现IProjectable This is implemented by the child MapShapeBase and once again, this child hasn't been generated yet. 这是由子MapShapeBase实现的,而且这个孩子还没有生成。

So, SetView never gets called and _NormalizedMercatorToViewport in MapShapeBase will have its default values and the calculations goes wrong the first time when you add a new item. 因此, SetView永远不会被调用,并且_NormalizedMercatorToViewport中的MapShapeBase将具有其默认值,并且在您添加新项目时第一次计算出错。


Workaround 解决方法

To workaround this problem we need to force a re-measure of the MapLayer . 要解决此问题,我们需要强制重新测量MapLayer This has to be done when a new ContentPresenter is added to the MapItemsControl but after the ContentPresenter has a visual child. 当将新的ContentPresenter添加到MapItemsControl但在ContentPresenter具有可视子项之后,必须执行此操作。

One way to force an update is to create an attached property which has the metadata-flags AffectsRender , AffectsArrange and AffectsMeasure set to true. 强制更新的一种方法是创建一个附加属性,其中元数据标志AffectsRenderAffectsArrangeAffectsMeasure设置为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 像这样使用它

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM