2025-08-12 23:02:11 +02:00
|
|
|
|
using System.Collections;
|
2025-08-11 23:01:22 +02:00
|
|
|
|
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.Media;
|
|
|
|
|
|
#elif WINUI
|
|
|
|
|
|
using Microsoft.UI.Xaml;
|
|
|
|
|
|
using Microsoft.UI.Xaml.Media;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
namespace MapControl
|
|
|
|
|
|
{
|
|
|
|
|
|
public interface IMapLayer : IMapElement
|
|
|
|
|
|
{
|
|
|
|
|
|
Brush MapBackground { get; }
|
|
|
|
|
|
Brush MapForeground { get; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public partial class MapBase
|
|
|
|
|
|
{
|
2025-08-12 10:57:44 +02:00
|
|
|
|
private bool hasMapLayerBackground;
|
|
|
|
|
|
private bool hasMapLayerForeground;
|
|
|
|
|
|
|
2025-08-11 23:01:22 +02:00
|
|
|
|
public static readonly DependencyProperty MapLayerProperty =
|
2025-08-12 10:57:44 +02:00
|
|
|
|
DependencyPropertyHelper.Register<MapBase, object>(nameof(MapLayer), null,
|
2025-08-11 23:01:22 +02:00
|
|
|
|
(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.
|
2025-08-12 23:02:11 +02:00
|
|
|
|
/// If the passed object is not a FrameworkElement, MapBase tries to locate a DataTemplate
|
|
|
|
|
|
/// resource for the object's type and generate a FrameworkElement from that DataTemplate.
|
|
|
|
|
|
/// If the FrameworkElement implements IMapLayer (like e.g. MapTileLayer or MapImageLayer),
|
|
|
|
|
|
/// its (non-null) MapBackground and MapForeground property values are used for the MapBase
|
|
|
|
|
|
/// Background and Foreground.
|
2025-08-11 23:01:22 +02:00
|
|
|
|
/// </summary>
|
2025-08-12 10:57:44 +02:00
|
|
|
|
public object MapLayer
|
2025-08-11 23:01:22 +02:00
|
|
|
|
{
|
2025-08-12 10:57:44 +02:00
|
|
|
|
get => GetValue(MapLayerProperty);
|
2025-08-11 23:01:22 +02:00
|
|
|
|
set => SetValue(MapLayerProperty, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public IEnumerable MapLayerItemsSource
|
|
|
|
|
|
{
|
|
|
|
|
|
get => (IEnumerable)GetValue(MapLayerItemsSourceProperty);
|
|
|
|
|
|
set => SetValue(MapLayerItemsSourceProperty, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 10:57:44 +02:00
|
|
|
|
private void MapLayerPropertyChanged(object oldLayer, object newLayer)
|
2025-08-11 23:01:22 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (oldLayer != null)
|
|
|
|
|
|
{
|
2025-08-12 23:02:11 +02:00
|
|
|
|
if (Children.Count > 0 &&
|
|
|
|
|
|
(Children[0] == oldLayer as FrameworkElement ||
|
|
|
|
|
|
((FrameworkElement)Children[0]).DataContext == oldLayer))
|
2025-08-11 23:01:22 +02:00
|
|
|
|
{
|
2025-08-12 11:33:39 +02:00
|
|
|
|
Children.RemoveAt(0);
|
2025-08-11 23:01:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 10:57:44 +02:00
|
|
|
|
if (hasMapLayerBackground)
|
2025-08-11 23:01:22 +02:00
|
|
|
|
{
|
2025-08-12 10:57:44 +02:00
|
|
|
|
ClearValue(BackgroundProperty);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (hasMapLayerForeground)
|
|
|
|
|
|
{
|
|
|
|
|
|
ClearValue(ForegroundProperty);
|
2025-08-11 23:01:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 10:57:44 +02:00
|
|
|
|
hasMapLayerBackground = false;
|
|
|
|
|
|
hasMapLayerForeground = false;
|
|
|
|
|
|
|
2025-08-11 23:01:22 +02:00
|
|
|
|
if (newLayer != null)
|
|
|
|
|
|
{
|
2025-08-12 23:02:11 +02:00
|
|
|
|
if (Children.Count == 0 ||
|
|
|
|
|
|
(Children[0] != newLayer as FrameworkElement &&
|
|
|
|
|
|
((FrameworkElement)Children[0]).DataContext != newLayer))
|
2025-08-11 23:01:22 +02:00
|
|
|
|
{
|
2025-08-12 11:33:39 +02:00
|
|
|
|
Children.Insert(0, GetMapLayer(newLayer));
|
2025-08-11 23:01:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 23:02:11 +02:00
|
|
|
|
if (Children[0] is IMapLayer mapLayer)
|
2025-08-11 23:01:22 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (mapLayer.MapBackground != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Background = mapLayer.MapBackground;
|
2025-08-12 10:57:44 +02:00
|
|
|
|
hasMapLayerBackground = true;
|
2025-08-11 23:01:22 +02:00
|
|
|
|
}
|
2025-08-12 10:57:44 +02:00
|
|
|
|
|
2025-08-11 23:01:22 +02:00
|
|
|
|
if (mapLayer.MapForeground != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Foreground = mapLayer.MapForeground;
|
2025-08-12 10:57:44 +02:00
|
|
|
|
hasMapLayerForeground = true;
|
2025-08-11 23:01:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2025-08-12 10:57:44 +02:00
|
|
|
|
var mapLayers = items.Cast<object>().Select(GetMapLayer).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
if (mapLayers.Count > 0)
|
|
|
|
|
|
{
|
2025-08-11 23:01:22 +02:00
|
|
|
|
#if WPF
|
2025-08-12 10:57:44 +02:00
|
|
|
|
// Execute at DispatcherPriority.DataBind to ensure that all bindings are evaluated.
|
|
|
|
|
|
Dispatcher.Invoke(() => AddMapLayers(mapLayers, index), DispatcherPriority.DataBind);
|
2025-08-11 23:01:22 +02:00
|
|
|
|
#else
|
2025-08-12 10:57:44 +02:00
|
|
|
|
AddMapLayers(mapLayers, index);
|
2025-08-11 23:01:22 +02:00
|
|
|
|
#endif
|
2025-08-12 10:57:44 +02:00
|
|
|
|
}
|
2025-08-11 23:01:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 10:57:44 +02:00
|
|
|
|
private void AddMapLayers(List<FrameworkElement> mapLayers, int index)
|
2025-08-11 23:01:22 +02:00
|
|
|
|
{
|
2025-08-12 11:33:39 +02:00
|
|
|
|
foreach (var mapLayer in mapLayers)
|
2025-08-12 10:57:44 +02:00
|
|
|
|
{
|
2025-08-12 11:33:39 +02:00
|
|
|
|
Children.Insert(index, mapLayer);
|
|
|
|
|
|
|
|
|
|
|
|
if (index == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
MapLayer = mapLayer;
|
|
|
|
|
|
}
|
2025-08-11 23:01:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void RemoveMapLayers(IEnumerable items, int index)
|
|
|
|
|
|
{
|
2025-08-12 11:33:39 +02:00
|
|
|
|
foreach (var _ in items)
|
|
|
|
|
|
{
|
|
|
|
|
|
Children.RemoveAt(index);
|
|
|
|
|
|
}
|
2025-08-11 23:01:22 +02:00
|
|
|
|
|
|
|
|
|
|
if (index == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
MapLayer = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 10:57:44 +02:00
|
|
|
|
private FrameworkElement GetMapLayer(object item)
|
2025-08-11 23:01:22 +02:00
|
|
|
|
{
|
|
|
|
|
|
FrameworkElement mapLayer = null;
|
|
|
|
|
|
|
|
|
|
|
|
if (item != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
mapLayer = item as FrameworkElement ?? TryLoadDataTemplate(item);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 10:57:44 +02:00
|
|
|
|
return mapLayer ?? new MapTileLayer();
|
2025-08-11 23:01:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2025-08-12 10:57:44 +02:00
|
|
|
|
if (TryFindResource(this, item.GetType().FullName) is DataTemplate template)
|
2025-08-11 23:01:22 +02:00
|
|
|
|
{
|
|
|
|
|
|
element = (FrameworkElement)template.LoadContent();
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
if (element != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
element.DataContext = item;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return element;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#if UWP || WINUI
|
2025-08-12 10:57:44 +02:00
|
|
|
|
private static object TryFindResource(FrameworkElement element, object key)
|
2025-08-11 23:01:22 +02:00
|
|
|
|
{
|
2025-08-12 10:57:44 +02:00
|
|
|
|
return element.Resources.ContainsKey(key)
|
|
|
|
|
|
? element.Resources[key]
|
|
|
|
|
|
: element.Parent is FrameworkElement parent
|
|
|
|
|
|
? TryFindResource(parent, key)
|
|
|
|
|
|
: null;
|
2025-08-11 23:01:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2025-08-12 10:57:44 +02:00
|
|
|
|
}
|
2025-08-11 23:01:22 +02:00
|
|
|
|
}
|