2012-06-08 9 views
14

Pracuję nad tym projektem powierzchni, w którym mamy kontrolę mapowania bing i gdzie chcemy narysować polilinię na mapie, używając wiązania danych.WPF bing maps kontroluj polilinie/wielokąty nie rysuj na pierwszym dodaj do kolekcji

Dziwne zachowanie, które się pojawia, polega na tym, że po kliknięciu przycisku Dodaj nic nie dzieje się na mapie. Jeśli przesunę trochę mapę, polilinia zostanie narysowana na mapie. Innym scenariuszem tego rodzaju prac jest kliknięcie raz przycisku dodawania, nic się nie dzieje, kliknij ponownie, oba rysunki są rysowane. (W mojej kolekcji ręcznej mam 4 CollectionCollections), tak samo dzieje się w przypadku trzeciego kliknięcia i czwartego kliknięcia, gdzie ponownie rysowane są obie linie.

Nie mam pojęcia, gdzie szukać, aby to naprawić. Próbowałem subskrybować zdarzenia Layoutupdated, które występują w obu przypadkach. Dodano także zdarzenie collectionchanged do obserwacyjnego elementu, aby zobaczyć, czy wyzwolenie jest wyzwalane, i tak jest wyzwalane. Inną rzeczą, którą próbowałem, była zmiana polilinii na pinezkę i pobranie pierwszej lokalizacji z kolekcji lokalizacji w potoku pipelineviewmodel, niż działa oczekiwanie.

Przesłałem sample project, jeśli chcesz zobaczyć, co się dzieje.

Naprawdę mam nadzieję, że ktoś może wskazać mi właściwy kierunek, ponieważ nie mam już pojęcia.

Poniżej znajdziesz kod, który napisałem:

Mam następujące ViewModels:

MainViewModel

public class MainViewModel 
{ 
    private ObservableCollection<PipelineViewModel> _pipelines; 

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

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

A PipelineViewModel który posiada kolekcję Lokalizacje który implementuje INotifyPropertyChanged :

Pipelin eViewModel

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

Moje XAML wygląda następująco:

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

A w naszym kodzie jesteśmy utworzenia wiązania i zdarzenie click tak:

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

nie mogę pomóc w tej sprawie, ale przetestowaniu przykładowy projekt; zrobić kilka prób i błędów (Unieważnij, wymuszaj przesuwanie mapy), ale także nie masz pojęcia, dlaczego nie działa. Wszystko, co zaimplementowałeś, wygląda dobrze. Ale oto kilka ustaleń: Dodawanie MapPolyline z CodeBehind działa dobrze. Jeśli użyjesz innego elementu, takiego jak Pushpin, to również działa dobrze. Tak więc problem dotyczy wszystkich rzeczy, które dziedziczą po MapShapeBase.A to MapPolyline i MapPolygon. Zauważyłem to za pomocą Reflectora i próbowałem porównać implementację Pushpin z implementacją MapPolyline. – SvenG

+0

Nie mogę zainwestować więcej czasu, ale gdybym mógł, debugowałem kod odbicia i sprawdzałem, dlaczego Pinezka jest odświeżana poprawnie, ale MapPolyLine/MapPolygon nie jest. – SvenG

+0

Cześć SvenG, Dzięki za poświęcony czas na to. Tak, widziałem, że pinezki działają dobrze. Wywołałem również metodę UpdateLayout() na MapItemsControl i dodałem pustą UIElement do warstwy, a ona pokaże polilinię. Nadal wiesz, dlaczego to nie działa :( – ChristiaanV

Odpowiedz

14

zrobiłem kilka kopanie w tym problemie i stwierdzono, że jest błąd w implementacji Map. Ja również obejście dla niej, które można używać tak jak to

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

włączyłem tę poprawkę w aplikacji próbki i wysłał go tutaj: SurfaceApplication3.zip


Wizualny drzewa dla każdego ContentPresenter wygląda następująco

enter image description here

po dodaniu nowego elementu do zbiorach Polygon dostaje th początkowo błędnie Points. Zamiast wartości takich jak 59, 29 dostajemy coś takiego jak 0.0009, 0.00044.

Punkty są obliczane w MeasureOverride w MapShapeBase i część, która robi obliczenie wygląda następująco

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

Początkowo _NormalizedMercatorToViewport będą miały swoje wartości domyślne (wszystko jest ustawione na 0) więc obliczenia idzie wszystko źle. _NormalizedMercatorToViewport zostaje ustawiony w metodzie SetView, która jest wywoływana z MeasureOverride w MapLayer.

MeasureOverride w MapLayer ma następujące dwa instrukcje if.

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

to wychodzi jak false ponieważ ContentPresenter nie ma jeszcze dziecka wizualny, to wciąż jest generowany. To jest problem.

Drugi wygląda następująco

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

to wychodzi jak false a także dlatego, że element, który jest ContentPresenter, nie implementuje IProjectable. Jest to realizowane przez dziecko MapShapeBase i jeszcze raz to dziecko nie zostało jeszcze wygenerowane.

Tak więc, SetView nigdy nie zostanie wywołana, a _NormalizedMercatorToViewport w MapShapeBase będzie mieć domyślne wartości, a obliczenia pójdą źle po raz pierwszy po dodaniu nowego elementu.


Obejście

Aby obejść ten problem, musimy wymusić ponowny pomiar MapLayer. Należy to zrobić, gdy nowy ContentPresenter zostanie dodany do MapItemsControl, ale po ContentPresenter ma wizualne dziecko.

Jednym ze sposobów wymuszenia aktualizacji jest utworzenie załączonej właściwości, która ma flagi metadanych: AffectsRender, AffectsArrange i AffectsMeasure ustawiona na wartość true. Następnie zmieniamy wartość tej właściwości za każdym razem, gdy chcemy dokonać aktualizacji.

Oto przywiązane zachowanie, które to robi. Używać go tak jak to

<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); 
    } 
} 
+0

@ ChristhiaanV: Jakiekolwiek szczęście z obejściem, które zasugerowałem? Jeśli masz jakieś pytania dotyczące "błędu", jestem szczęśliwy Problem polega nie na twojej implementacji, ale na implementacji 'Mapy' firmy Microsoft. Myślę, że jest to problem, który trzeba naprawić na końcu i jedyny sposób aby to działało, dopóki nie zrobi to, aby użyć jakiegoś obejścia problemu –

+0

Wow, to jest świetna odpowiedź! Naprawdę z tego zadowolona! – ChristiaanV

+0

Poszukujełem rozwiązania tego problemu przez jakiś czas i chociaż były różne sugestie, że jest to jedyna, która (jak dotąd) działała niezawodnie.Jednak jedną rzeczą, być może niepoprawnie strukturyzuję mój XAML, ale jeśli umieścisz MapItemsControl w MapLayer, to poprawka nie działa. Lly potrzebuję wielu warstw dla tego, co robię. –

Powiązane problemy