Added MapBase.MapLayerItemsSource property

This commit is contained in:
ClemensFischer 2025-08-11 23:01:22 +02:00
parent 0b5d1e439f
commit 2194f08a74
6 changed files with 275 additions and 70 deletions

View file

@ -0,0 +1,241 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
#if WPF
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
#elif UWP
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
#elif WINUI
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
#endif
namespace MapControl
{
public interface IMapLayer : IMapElement
{
Brush MapBackground { get; }
Brush MapForeground { get; }
}
public partial class MapBase
{
public static readonly DependencyProperty MapLayerProperty =
DependencyPropertyHelper.Register<MapBase, FrameworkElement>(nameof(MapLayer), null,
(map, oldValue, newValue) => map.MapLayerPropertyChanged(oldValue, newValue));
public static readonly DependencyProperty MapLayerItemsSourceProperty =
DependencyPropertyHelper.Register<MapBase, IEnumerable>(nameof(MapLayerItemsSource), null,
(map, oldValue, newValue) => map.MapLayerItemsSourcePropertyChanged(oldValue, newValue));
/// <summary>
/// Gets or sets the base map layer, which is added as first element to the Children collection.
/// If the layer implements IMapLayer (like MapTileLayer or MapImageLayer), its (non-null) MapBackground
/// and MapForeground property values are used for the MapBase Background and Foreground properties.
/// </summary>
public FrameworkElement MapLayer
{
get => (FrameworkElement)GetValue(MapLayerProperty);
set => SetValue(MapLayerProperty, value);
}
public IEnumerable MapLayerItemsSource
{
get => (IEnumerable)GetValue(MapLayerItemsSourceProperty);
set => SetValue(MapLayerItemsSourceProperty, value);
}
private void MapLayerPropertyChanged(FrameworkElement oldLayer, FrameworkElement newLayer)
{
if (oldLayer != null)
{
if (Children.Count > 0 && Children[0] == oldLayer)
{
Children.RemoveAt(0);
}
if (oldLayer is IMapLayer mapLayer)
{
if (mapLayer.MapBackground != null)
{
ClearValue(BackgroundProperty);
}
if (mapLayer.MapForeground != null)
{
ClearValue(ForegroundProperty);
}
}
}
if (newLayer != null)
{
if (Children.Count == 0 || Children[0] != newLayer)
{
Children.Insert(0, newLayer);
}
if (newLayer is IMapLayer mapLayer)
{
if (mapLayer.MapBackground != null)
{
Background = mapLayer.MapBackground;
}
if (mapLayer.MapForeground != null)
{
Foreground = mapLayer.MapForeground;
}
}
}
}
private void MapLayerItemsSourcePropertyChanged(IEnumerable oldItems, IEnumerable newItems)
{
if (oldItems != null)
{
if (oldItems is INotifyCollectionChanged incc)
{
incc.CollectionChanged -= MapLayerItemsSourceCollectionChanged;
}
RemoveMapLayers(oldItems, 0);
}
if (newItems != null)
{
if (newItems is INotifyCollectionChanged incc)
{
incc.CollectionChanged += MapLayerItemsSourceCollectionChanged;
}
AddMapLayers(newItems, 0);
}
}
private void MapLayerItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddMapLayers(e.NewItems, e.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Remove:
RemoveMapLayers(e.OldItems, e.OldStartingIndex);
break;
case NotifyCollectionChangedAction.Replace:
RemoveMapLayers(e.OldItems, e.OldStartingIndex);
AddMapLayers(e.NewItems, e.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Reset:
break;
default:
break;
}
}
private void AddMapLayers(IEnumerable items, int index)
{
var mapLayers = items.Cast<object>().Select(CreateMapLayer).ToList();
#if WPF
// Execute at DispatcherPriority.DataBind to ensure that all bindings are evaluated.
Dispatcher.Invoke(() => AddMapLayers(mapLayers, index), DispatcherPriority.DataBind);
#else
AddMapLayers(mapLayers, index);
#endif
}
private void AddMapLayers(IEnumerable<FrameworkElement> mapLayers, int index)
{
foreach (var mapLayer in mapLayers)
{
Children.Insert(index, mapLayer);
if (index++ == 0)
{
MapLayer = mapLayer;
}
}
}
private void RemoveMapLayers(IEnumerable items, int index)
{
Children.RemoveRange(index, items.Cast<object>().Count());
if (index == 0)
{
MapLayer = null;
}
}
private FrameworkElement CreateMapLayer(object item)
{
FrameworkElement mapLayer = null;
if (item != null)
{
mapLayer = item as FrameworkElement ?? TryLoadDataTemplate(item);
}
return mapLayer ?? new MapControl.MapPanel();
}
private FrameworkElement TryLoadDataTemplate(object item)
{
FrameworkElement element = null;
#if AVALONIA
if (this.TryFindResource(item.GetType().FullName, out object value) &&
value is Avalonia.Markup.Xaml.Templates.DataTemplate template)
{
element = template.Build(item);
}
#elif WPF
if (TryFindResource(new DataTemplateKey(item.GetType())) is DataTemplate template)
{
element = (FrameworkElement)template.LoadContent();
}
#else
if (this.TryFindResource(item.GetType().FullName) is DataTemplate template)
{
element = (FrameworkElement)template.LoadContent();
}
#endif
if (element != null)
{
element.DataContext = item;
}
return element;
}
}
#if UWP || WINUI
internal static class MapBaseExtensions
{
public static void RemoveRange(this UIElementCollection elements, int index, int count)
{
while (--count >= 0)
{
elements.RemoveAt(index);
}
}
public static object TryFindResource(this FrameworkElement element, object key)
{
return element.Resources.ContainsKey(key)
? element.Resources[key]
: element.Parent is FrameworkElement parent
? TryFindResource(parent, key)
: null;
}
}
#endif
}

View file

@ -12,12 +12,6 @@ using Microsoft.UI.Xaml.Media;
namespace MapControl
{
public interface IMapLayer : IMapElement
{
Brush MapBackground { get; }
Brush MapForeground { get; }
}
/// <summary>
/// The map control. Displays map content provided by one or more tile or image layers,
/// such as MapTileLayerBase or MapImageLayer instances.
@ -42,10 +36,6 @@ namespace MapControl
public static readonly DependencyProperty AnimationDurationProperty =
DependencyPropertyHelper.Register<MapBase, TimeSpan>(nameof(AnimationDuration), TimeSpan.FromSeconds(0.3));
public static readonly DependencyProperty MapLayerProperty =
DependencyPropertyHelper.Register<MapBase, FrameworkElement>(nameof(MapLayer), null,
(map, oldValue, newValue) => map.MapLayerPropertyChanged(oldValue, newValue));
public static readonly DependencyProperty MapProjectionProperty =
DependencyPropertyHelper.Register<MapBase, MapProjection>(nameof(MapProjection), new WebMercatorProjection(),
(map, oldValue, newValue) => map.MapProjectionPropertyChanged(newValue));
@ -84,17 +74,6 @@ namespace MapControl
set => SetValue(AnimationDurationProperty, value);
}
/// <summary>
/// Gets or sets the base map layer, which is added as first element to the Children collection.
/// If the layer implements IMapLayer (like MapTileLayer or MapImageLayer), its (non-null) MapBackground
/// and MapForeground property values are used for the MapBase Background and Foreground properties.
/// </summary>
public FrameworkElement MapLayer
{
get => (FrameworkElement)GetValue(MapLayerProperty);
set => SetValue(MapLayerProperty, value);
}
/// <summary>
/// Gets or sets the MapProjection used by the map control.
/// </summary>
@ -440,49 +419,6 @@ namespace MapControl
internalPropertyChange = false;
}
private void MapLayerPropertyChanged(FrameworkElement oldLayer, FrameworkElement newLayer)
{
if (oldLayer != null)
{
if (Children.Count > 0 && Children[0] == oldLayer)
{
Children.RemoveAt(0);
}
if (oldLayer is IMapLayer mapLayer)
{
if (mapLayer.MapBackground != null)
{
ClearValue(BackgroundProperty);
}
if (mapLayer.MapForeground != null)
{
ClearValue(ForegroundProperty);
}
}
}
if (newLayer != null)
{
if (Children.Count == 0 || Children[0] != newLayer)
{
Children.Insert(0, newLayer);
}
if (newLayer is IMapLayer mapLayer)
{
if (mapLayer.MapBackground != null)
{
Background = mapLayer.MapBackground;
}
if (mapLayer.MapForeground != null)
{
Foreground = mapLayer.MapForeground;
}
}
}
}
private void MapProjectionPropertyChanged(MapProjection projection)
{
maxLatitude = 90d;

View file

@ -41,8 +41,11 @@ namespace MapControl
{
mapElement.ParentMap = newValue;
}
},
true); // inherits
#if UWP || WINUI
});
#else
}, true); // inherits
#endif
public MapPanel()
{

View file

@ -107,6 +107,9 @@
<Compile Include="..\Shared\MapBase.cs">
<Link>MapBase.cs</Link>
</Compile>
<Compile Include="..\Shared\MapBase.MapLayer.cs">
<Link>MapBase.MapLayer.cs</Link>
</Compile>
<Compile Include="..\Shared\MapBorderPanel.cs">
<Link>MapBorderPanel.cs</Link>
</Compile>
@ -281,6 +284,9 @@
<Compile Include="..\WinUI\Tile.WinUI.cs">
<Link>Tile.WinUI.cs</Link>
</Compile>
<Compile Include="..\WinUI\TileSourceConverter.cs">
<Link>TileSourceConverter.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TileImageLoader.UWP.cs" />
<EmbeddedResource Include="Properties\MapControl.UWP.rd.xml" />

View file

@ -5,8 +5,6 @@ using Windows.UI.Xaml;
using Microsoft.UI.Xaml;
#endif
#pragma warning disable IDE0060 // Remove unused parameter
namespace MapControl
{
public static class DependencyPropertyHelper
@ -15,8 +13,7 @@ namespace MapControl
string name,
Type ownerType,
TValue defaultValue = default,
Action<FrameworkElement, TValue, TValue> changed = null,
bool inherits = false) // unused in WinUI/UWP
Action<FrameworkElement, TValue, TValue> changed = null)
{
var metadata = changed == null
? new PropertyMetadata(defaultValue)

View file

@ -0,0 +1,22 @@
using System;
#if UWP
using Windows.UI.Xaml.Data;
#elif WINUI
using Microsoft.UI.Xaml.Data;
#endif
namespace MapControl
{
public class TileSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return TileSource.Parse(value.ToString());
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotSupportedException();
}
}
}