14

私は、データ バインディングを使用して、bing マップ コントロールがあり、マップ上にポリラインを描画したいこのサーフェス プロジェクトに取り組んでいます。

発生している奇妙な動作は、[追加] ボタンをクリックしてもマップ上で何も起こらないことです。地図を少し動かすと、ポリラインが地図上に描画されます。動作するもう 1 つのシナリオは、追加ボタンを 1 回クリックしても何も起こらず、もう一度クリックすると、両方のポリラインが描画されます。(私の手動コレクションには 4 つの LocationCollections があります) 3 回目のクリックと 4 回目のクリックでも同じことが起こり、両方の線が描画されます。

これを修正するためにどこを探すべきかまったくわかりません。どちらの場合も発生する Layoutupdated イベントをサブスクライブしようとしました。また、observablecollection に collectionchanged イベントを追加して、追加がトリガーされるかどうかを確認しました。はい、トリガーされます。私が試したもう1つのことは、ポリラインをプッシュピンに変更し、パイプラインビューモデルの場所のコレクションから最初の場所を取得することです.

何が起こっているのかを自分で確認したい場合のために、サンプル プロジェクトをアップロードしました。

もう手がかりがないので、誰かが私を正しい方向に向けてくれることを本当に願っています。

以下に、私が書いたコードを示します。

次のビューモデルがあります。

MainViewModel

public class MainViewModel
{
    private ObservableCollection<PipelineViewModel> _pipelines;

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

    public MainViewModel()
    {
        _pipelines = new ObservableCollection<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");
        }
    }
}

私の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;
}
4

1 に答える 1

17

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 に設定されている)があるため、計算はすべて間違っています。inから呼び出される_NormalizedMercatorToViewportメソッドで設定されます。SetViewMeasureOverrideMapLayer

MeasureOverridein にMapLayerは、次の 2 つの if ステートメントがあります。

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

これは、ビジュアルの子がまだ生成されていないためですfalseこれが問題です。ContentPresenter

2枚目はこんな感じ

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

である要素が実装されていないため、これfalseも同様に発生します。これは子によって実装されますが、この子はまだ生成されていません。ContentPresenterIProjectableMapShapeBase

そのため、SetView呼び出されることはなく_NormalizedMercatorToViewportMapShapeBaseデフォルト値があり、新しいアイテムを初めて追加するときに計算がうまくいきません。


回避策

この問題を回避するには、 の再測定を強制する必要がありMapLayerます。これは、新しいContentPresenterが に追加されたときに実行する必要がありますが、 にビジュアルの子が追加されMapItemsControlた後です。ContentPresenter

更新を強制する 1 つの方法は、metadata-flags を持つ添付プロパティを作成し、AffectsRendertrueAffectsArrangeAffectsMeasure設定することです。次に、更新を行うたびに、このプロパティの値を変更するだけです。

これを行う添付の動作を次に示します。このように使用してください

<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);
    }
}
于 2012-06-17T10:06:06.680 に答える