2012-06-08 17 views
14

Estoy trabajando en este proyecto de superficie donde tenemos un control de mapas bing y donde nos gustaría dibujar polilíneas en el mapa, mediante el enlace de datos.WPF mapas de control polilíneas/polígonos no dibujar en primer lugar agregar a la colección

El extraño comportamiento que ocurre es que cuando hago clic en el botón Agregar, no ocurre nada en el mapa. Si muevo el mapa un poco, la polilínea se dibuja en el mapa. Otro escenario que funciona, haga clic en el botón Agregar una vez, no pasa nada, haga clic de nuevo. Se dibujan ambas polilíneas. (En mi colección manual tengo 4 LocationCollections) así que lo mismo ocurre con el tercer clic y el cuarto clic donde nuevamente se dibujan ambas líneas.

No tengo ni idea de dónde buscar para solucionarlo. Intenté suscribirme a los eventos Layoutupdated, que ocurren en ambos casos. También se agregó un evento de colección cambiada a la colección observable para ver si el complemento se activó, y sí se desencadena. Otra cosa que probé es cambiar la polilínea a chincheta y tomar la primera ubicación de la colección de ubicaciones en el modelo de pipelineview, de lo que está funcionando.

He subido un sample project para ver qué está pasando.

Realmente espero que alguien pueda señalarme en la dirección correcta, porque ya no tengo ni idea.

A continuación encontrará el código que he escrito:

tengo los siguientes ViewModels:

MainViewModel

public class MainViewModel 
{ 
    private ObservableCollection<PipelineViewModel> _pipelines; 

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

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

Y el PipelineViewModel que tiene la colección de ubicaciones que implementa 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"); 
     } 
    } 
} 

Mi XAML se parece a continuación:

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

Y en nuestro código subyacente que son la creación de la unión y el evento click de esta manera:

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

No puedo ayudar en esto, pero he probado el proyecto de ejemplo; hizo algunas cosas de prueba y error (Invalidar, forzar el movimiento del mapa) pero tampoco tiene idea de por qué no está funcionando. Todo lo que has implementado se ve bien. Pero aquí hay algunos hallazgos: Agregar MapPolyline de CodeBehind funciona bien. Si usa otro elemento como Pushpin, también funciona bien. Entonces, el problema tiene que ver con todo lo que hereda de MapShapeBase.Y eso son MapPolyline y MapPolygon. Lo busqué a través de Reflector e intenté comparar la implementación de Pushpin con la implementación de MapPolyline. – SvenG

+0

No puedo invertir más tiempo, pero si pudiera, podría depurar el código reflejado y echar un vistazo por qué se actualiza correctamente un marcador, pero MapPolyLine/MapPolygon no lo está. – SvenG

+0

Hola SvenG, gracias por tu tiempo mirándolo. Sí, he visto que los marcadores funcionan bien. También he llamado al método UpdateLayout() en MapItemsControl y luego agregué un UIElement vacío a la capa y se mostrará la polilínea. Todavía tengo que decir por qué no está funcionando :( – ChristiaanV

Respuesta

14

Hice algo de investigación sobre este problema y descubrió que hay un error en la implementación Map. También hice una solución para él que puede ser utilizado como esto

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

Incluí esta revisión de la aplicación de la muestra y subido aquí: SurfaceApplication3.zip


El árbol visual para cada ContentPresenter tiene este aspecto

enter image description here

Cuando se agrega un nuevo elemento a la colección del Polygon obtiene º e incorrecto Points inicialmente. En lugar de valores como 59, 29 se pone algo así como 0.0009, 0.00044.

Los puntos se calculan en MeasureOverride en MapShapeBase y la parte que hace el cálculo se parece a esto

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

Inicialmente, _NormalizedMercatorToViewport tendrá sus valores por defecto (todo lo que se establece en 0) por lo que los cálculos va todo mal. _NormalizedMercatorToViewport se establece en el método SetView que se llama desde MeasureOverride en MapLayer.

MeasureOverride en MapLayer tiene las siguientes dos sentencias if.

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

Esto sale como false porque el ContentPresenter no ha conseguido todavía un niño visual, todavía se está generando. Este es el problema.

El segundo se parece a esto

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

Esto sale como false así porque el elemento, que es un ContentPresenter, no implementa IProjectable. Esto es implementado por el niño MapShapeBase y una vez más, este niño no se ha generado aún.

Por lo tanto, nunca se pone SetView llamado y _NormalizedMercatorToViewport en MapShapeBase tendrá sus valores predeterminados y los cálculos que va mal la primera vez cuando se agrega un nuevo elemento.


Solución

Para solucionar este problema es necesario forzar una re-medida de la MapLayer. Esto tiene que hacerse cuando se agrega un nuevo ContentPresenter al MapItemsControl pero después de que el ContentPresenter tiene un elemento secundario visual.

Una forma de forzar una actualización es crear una propiedad adjunta que tenga las marcas de metadatos AffectsRender, AffectsArrange y AffectsMeasure en verdadero. Luego, simplemente cambiamos el valor de esta propiedad cada vez que queremos hacer la actualización.

Aquí hay un comportamiento adjunto que hace esto. Utilizar de esta manera

<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

@ChristiaanV: ¿Alguna suerte con la solución que sugerí? Si tienes alguna pregunta sobre el "error", estoy contento. para tratar de responder. De la forma en que lo veo, el problema no es con su implementación sino con la implementación del 'Mapa' de Microsoft. Creo que este es un problema que debe solucionarse por su parte y de la única manera conseguir que funcione hasta que lo hagan es utilizar algún tipo de solución. –

+0

¡Guau, esa es una gran respuesta! ¡Realmente feliz! – ChristiaanV

+0

He estado buscando una solución a este problema durante un tiempo y aunque ha habido varios sugerencias, esta es la única que (hasta ahora) ha funcionado de manera confiable. Una cosa es: tal vez estoy estructurando mi XAML incorrectamente, pero si coloca el MapItemsControl dentro de un MapLayer, entonces la solución no funciona. Afortunadamente no lo hago Sólo necesito las capas múltiples para lo que estoy haciendo. –

Cuestiones relacionadas