diff --git a/MapControl/Shared/MapBase.MapLayer.cs b/MapControl/Shared/MapBase.MapLayer.cs new file mode 100644 index 00000000..b0d75522 --- /dev/null +++ b/MapControl/Shared/MapBase.MapLayer.cs @@ -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(nameof(MapLayer), null, + (map, oldValue, newValue) => map.MapLayerPropertyChanged(oldValue, newValue)); + + public static readonly DependencyProperty MapLayerItemsSourceProperty = + DependencyPropertyHelper.Register(nameof(MapLayerItemsSource), null, + (map, oldValue, newValue) => map.MapLayerItemsSourcePropertyChanged(oldValue, newValue)); + + /// + /// 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. + /// + 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().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 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().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 +} diff --git a/MapControl/Shared/MapBase.cs b/MapControl/Shared/MapBase.cs index 0f6d83d0..2363e0e0 100644 --- a/MapControl/Shared/MapBase.cs +++ b/MapControl/Shared/MapBase.cs @@ -12,12 +12,6 @@ using Microsoft.UI.Xaml.Media; namespace MapControl { - public interface IMapLayer : IMapElement - { - Brush MapBackground { get; } - Brush MapForeground { get; } - } - /// /// 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(nameof(AnimationDuration), TimeSpan.FromSeconds(0.3)); - public static readonly DependencyProperty MapLayerProperty = - DependencyPropertyHelper.Register(nameof(MapLayer), null, - (map, oldValue, newValue) => map.MapLayerPropertyChanged(oldValue, newValue)); - public static readonly DependencyProperty MapProjectionProperty = DependencyPropertyHelper.Register(nameof(MapProjection), new WebMercatorProjection(), (map, oldValue, newValue) => map.MapProjectionPropertyChanged(newValue)); @@ -84,17 +74,6 @@ namespace MapControl set => SetValue(AnimationDurationProperty, value); } - /// - /// 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. - /// - public FrameworkElement MapLayer - { - get => (FrameworkElement)GetValue(MapLayerProperty); - set => SetValue(MapLayerProperty, value); - } - /// /// Gets or sets the MapProjection used by the map control. /// @@ -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; diff --git a/MapControl/Shared/MapPanel.cs b/MapControl/Shared/MapPanel.cs index ac5f2ce6..ccc0f9cb 100644 --- a/MapControl/Shared/MapPanel.cs +++ b/MapControl/Shared/MapPanel.cs @@ -41,8 +41,11 @@ namespace MapControl { mapElement.ParentMap = newValue; } - }, - true); // inherits +#if UWP || WINUI + }); +#else + }, true); // inherits +#endif public MapPanel() { diff --git a/MapControl/UWP/MapControl.UWP.csproj b/MapControl/UWP/MapControl.UWP.csproj index 4538808c..7e25001a 100644 --- a/MapControl/UWP/MapControl.UWP.csproj +++ b/MapControl/UWP/MapControl.UWP.csproj @@ -107,6 +107,9 @@ MapBase.cs + + MapBase.MapLayer.cs + MapBorderPanel.cs @@ -281,6 +284,9 @@ Tile.WinUI.cs + + TileSourceConverter.cs + diff --git a/MapControl/WinUI/DependencyPropertyHelper.WinUI.cs b/MapControl/WinUI/DependencyPropertyHelper.WinUI.cs index 8690cd70..0f2dfa75 100644 --- a/MapControl/WinUI/DependencyPropertyHelper.WinUI.cs +++ b/MapControl/WinUI/DependencyPropertyHelper.WinUI.cs @@ -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 changed = null, - bool inherits = false) // unused in WinUI/UWP + Action changed = null) { var metadata = changed == null ? new PropertyMetadata(defaultValue) diff --git a/MapControl/WinUI/TileSourceConverter.cs b/MapControl/WinUI/TileSourceConverter.cs new file mode 100644 index 00000000..3c700c6c --- /dev/null +++ b/MapControl/WinUI/TileSourceConverter.cs @@ -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(); + } + } +}