diff --git a/MapControl/Avalonia/BindingHelper.Avalonia.cs b/MapControl/Avalonia/BindingHelper.Avalonia.cs new file mode 100644 index 00000000..e9d87d45 --- /dev/null +++ b/MapControl/Avalonia/BindingHelper.Avalonia.cs @@ -0,0 +1,25 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// Copyright © 2024 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using Avalonia.Data; + +namespace MapControl +{ + internal static class BindingHelper + { + public static Binding CreateBinding(this object source, string property) + { + return new Binding + { + Source = source, + Path = property + }; + } + + public static void SetBinding(this AvaloniaObject target, AvaloniaProperty property, Binding binding) + { + target.Bind(property, binding); + } + } +} diff --git a/MapControl/Avalonia/MapControl.Avalonia.csproj b/MapControl/Avalonia/MapControl.Avalonia.csproj index 3fce9dc8..1c1f51a8 100644 --- a/MapControl/Avalonia/MapControl.Avalonia.csproj +++ b/MapControl/Avalonia/MapControl.Avalonia.csproj @@ -28,11 +28,9 @@ - - diff --git a/MapControl/Avalonia/MapOverlay.Avalonia.cs b/MapControl/Avalonia/MapOverlay.Avalonia.cs new file mode 100644 index 00000000..7ea7671d --- /dev/null +++ b/MapControl/Avalonia/MapOverlay.Avalonia.cs @@ -0,0 +1,77 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// Copyright © 2024 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using Avalonia.Collections; +using Avalonia.Controls.Documents; +using Avalonia.Controls.Shapes; +using Avalonia.Data; +using Avalonia.Media; + +namespace MapControl +{ + public partial class MapOverlay + { + public static readonly StyledProperty FontFamilyProperty = + DependencyPropertyHelper.AddOwner(TextElement.FontFamilyProperty); + + public static readonly StyledProperty FontSizeProperty = + DependencyPropertyHelper.AddOwner(TextElement.FontSizeProperty); + + public static readonly StyledProperty FontStyleProperty = + DependencyPropertyHelper.AddOwner(TextElement.FontStyleProperty); + + public static readonly StyledProperty FontStretchProperty = + DependencyPropertyHelper.AddOwner(TextElement.FontStretchProperty); + + public static readonly StyledProperty FontWeightProperty = + DependencyPropertyHelper.AddOwner(TextElement.FontWeightProperty); + + public static readonly StyledProperty ForegroundProperty = + DependencyPropertyHelper.AddOwner(TextElement.ForegroundProperty); + + public static readonly StyledProperty StrokeProperty = + DependencyPropertyHelper.AddOwner(Shape.StrokeProperty); + + public static readonly StyledProperty StrokeThicknessProperty = + DependencyPropertyHelper.Register(nameof(StrokeThickness), 1d); + + public static readonly StyledProperty> StrokeDashArrayProperty = + DependencyPropertyHelper.AddOwner>(Shape.StrokeDashArrayProperty); + + public static readonly StyledProperty StrokeDashOffsetProperty = + DependencyPropertyHelper.AddOwner(Shape.StrokeDashOffsetProperty); + + public static readonly StyledProperty StrokeLineCapProperty = + DependencyPropertyHelper.AddOwner(Shape.StrokeLineCapProperty); + + public static readonly StyledProperty StrokeLineJoinProperty = + DependencyPropertyHelper.AddOwner(Shape.StrokeJoinProperty); + + public static readonly StyledProperty StrokeMiterLimitProperty = + DependencyPropertyHelper.Register(nameof(StrokeMiterLimit)); + + protected override void OnInitialized() + { + base.OnInitialized(); + + if (Stroke == null) + { + this.SetBinding(StrokeProperty, this.CreateBinding(nameof(Foreground))); + } + } + + public Pen CreatePen() + { + return new Pen + { + Brush = Stroke, + Thickness = StrokeThickness, + LineJoin = StrokeLineJoin, + MiterLimit = StrokeMiterLimit, + LineCap = StrokeLineCap, + DashStyle = new DashStyle(StrokeDashArray, StrokeDashOffset) + }; + } + } +} diff --git a/MapControl/Shared/BindingHelper.cs b/MapControl/Shared/BindingHelper.cs index 5827242d..9b80b483 100644 --- a/MapControl/Shared/BindingHelper.cs +++ b/MapControl/Shared/BindingHelper.cs @@ -17,35 +17,13 @@ namespace MapControl { internal static class BindingHelper { - /// - /// Returns a Binding to the specified dependency property of a FrameworkElement. - /// If the source property is itself already bound, the method returns the existing Binding, - /// otherwise it creates one with sourceElement as Source and sourcePropertyName as Path. - /// - public static Binding GetOrCreateBinding( - this FrameworkElement sourceElement, DependencyProperty sourceProperty, string sourcePropertyName) + public static Binding CreateBinding(this FrameworkElement source, string property) { - var sourceBinding = sourceElement.GetBindingExpression(sourceProperty); - - return sourceBinding != null - ? sourceBinding.ParentBinding - : new Binding { Source = sourceElement, Path = new PropertyPath(sourcePropertyName) }; - } - - /// - /// Sets a Binding on the specified dependency property of targetElement, if the target property does - /// not yet have a value or a Binding assigned to it. The potentially assigned Binding is created by - /// GetOrCreateBinding(sourceElement, sourceProperty, sourcePropertyName). - /// - public static void SetBindingOnUnsetProperty( - this FrameworkElement targetElement, DependencyProperty targetProperty, - FrameworkElement sourceElement, DependencyProperty sourceProperty, string sourcePropertyName) - { - if (targetElement.GetValue(targetProperty) == null && - targetElement.GetBindingExpression(targetProperty) == null) + return new Binding { - targetElement.SetBinding(targetProperty, GetOrCreateBinding(sourceElement, sourceProperty, sourcePropertyName)); - } + Source = source, + Path = new PropertyPath(property) + }; } } } diff --git a/MapControl/Shared/MapOverlay.cs b/MapControl/Shared/MapOverlay.cs index 30273023..188bdf66 100644 --- a/MapControl/Shared/MapOverlay.cs +++ b/MapControl/Shared/MapOverlay.cs @@ -11,6 +11,9 @@ using Windows.UI.Xaml.Media; #elif WINUI using Microsoft.UI.Xaml.Media; using Windows.UI.Text; +#elif AVALONIA +using Avalonia.Media; +using DoubleCollection = System.Collections.Generic.IEnumerable; #endif namespace MapControl @@ -80,22 +83,10 @@ namespace MapControl set => SetValue(StrokeDashOffsetProperty, value); } - public PenLineCap StrokeDashCap + public PenLineCap StrokeLineCap { - get => (PenLineCap)GetValue(StrokeDashCapProperty); - set => SetValue(StrokeDashCapProperty, value); - } - - public PenLineCap StrokeStartLineCap - { - get => (PenLineCap)GetValue(StrokeStartLineCapProperty); - set => SetValue(StrokeStartLineCapProperty, value); - } - - public PenLineCap StrokeEndLineCap - { - get => (PenLineCap)GetValue(StrokeEndLineCapProperty); - set => SetValue(StrokeEndLineCapProperty, value); + get => (PenLineCap)GetValue(StrokeLineCapProperty); + set => SetValue(StrokeLineCapProperty, value); } public PenLineJoin StrokeLineJoin diff --git a/MapControl/Shared/MapScale.cs b/MapControl/Shared/MapScale.cs index a04e229b..37a31a04 100644 --- a/MapControl/Shared/MapScale.cs +++ b/MapControl/Shared/MapScale.cs @@ -7,20 +7,32 @@ using System.Globalization; #if WPF using System.Windows; using System.Windows.Controls; +using System.Windows.Data; using System.Windows.Media; using System.Windows.Shapes; #elif UWP using Windows.Foundation; using Windows.UI.Xaml; +using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Shapes; #elif WINUI using Windows.Foundation; using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Shapes; +#elif AVALONIA +using Avalonia.Data; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using DependencyProperty = Avalonia.AvaloniaProperty; +using HorizontalAlignment = Avalonia.Layout.HorizontalAlignment; +using VerticalAlignment = Avalonia.Layout.VerticalAlignment; +using PointCollection = System.Collections.Generic.List; #endif namespace MapControl @@ -53,11 +65,10 @@ namespace MapControl { base.SetParentMap(map); - line.SetBinding(Shape.StrokeProperty, this.GetOrCreateBinding(StrokeProperty, nameof(Stroke))); - line.SetBinding(Shape.StrokeThicknessProperty, this.GetOrCreateBinding(StrokeThicknessProperty, nameof(StrokeThickness))); - + line.SetBinding(Shape.StrokeProperty, this.CreateBinding(nameof(Stroke))); + line.SetBinding(Shape.StrokeThicknessProperty, this.CreateBinding(nameof(StrokeThickness))); #if UWP || WINUI - label.SetBinding(TextBlock.ForegroundProperty, this.GetOrCreateBinding(ForegroundProperty, nameof(Foreground))); + label.SetBinding(TextBlock.ForegroundProperty, this.CreateBinding(nameof(Foreground))); #endif } @@ -69,42 +80,44 @@ namespace MapControl protected override Size MeasureOverride(Size availableSize) { - var size = new Size(); double scale; - if (ParentMap != null && (scale = ParentMap.GetScale(ParentMap.Center).X) > 0d) + if (ParentMap == null || (scale = ParentMap.GetScale(ParentMap.Center).X) <= 0d) { - var length = MinWidth / scale; - var magnitude = Math.Pow(10d, Math.Floor(Math.Log10(length))); + return new Size(); + } - length = length / magnitude < 2d ? 2d * magnitude - : length / magnitude < 5d ? 5d * magnitude - : 10d * magnitude; + var length = MinWidth / scale; + var magnitude = Math.Pow(10d, Math.Floor(Math.Log10(length))); - size.Width = length * scale + StrokeThickness + Padding.Left + Padding.Right; - size.Height = 1.25 * FontSize + StrokeThickness + Padding.Top + Padding.Bottom; + length = length / magnitude < 2d ? 2d * magnitude + : length / magnitude < 5d ? 5d * magnitude + : 10d * magnitude; - var x1 = Padding.Left + StrokeThickness / 2d; - var x2 = size.Width - Padding.Right - StrokeThickness / 2d; - var y1 = size.Height / 2d; - var y2 = size.Height - Padding.Bottom - StrokeThickness / 2d; + var size = new Size( + length * scale + StrokeThickness + Padding.Left + Padding.Right, + 1.25 * FontSize + StrokeThickness + Padding.Top + Padding.Bottom); - line.Points = new PointCollection + var x1 = Padding.Left + StrokeThickness / 2d; + var x2 = size.Width - Padding.Right - StrokeThickness / 2d; + var y1 = size.Height / 2d; + var y2 = size.Height - Padding.Bottom - StrokeThickness / 2d; + + line.Points = new PointCollection { new Point(x1, y1), new Point(x1, y2), new Point(x2, y2), new Point(x2, y1) }; - line.Measure(size); + line.Measure(size); - label.Text = length >= 1000d - ? string.Format(CultureInfo.InvariantCulture, "{0:F0} km", length / 1000d) - : string.Format(CultureInfo.InvariantCulture, "{0:F0} m", length); - label.Width = size.Width; - label.Height = size.Height; - label.Measure(size); - } + label.Text = length >= 1000d + ? string.Format(CultureInfo.InvariantCulture, "{0:F0} km", length / 1000d) + : string.Format(CultureInfo.InvariantCulture, "{0:F0} m", length); + label.Width = size.Width; + label.Height = size.Height; + label.Measure(size); return size; } diff --git a/MapControl/WPF/MapOverlay.WPF.cs b/MapControl/WPF/MapOverlay.WPF.cs index a7bfadee..d492efc6 100644 --- a/MapControl/WPF/MapOverlay.WPF.cs +++ b/MapControl/WPF/MapOverlay.WPF.cs @@ -4,6 +4,7 @@ using System; using System.Windows; +using System.Windows.Data; using System.Windows.Documents; using System.Windows.Media; using System.Windows.Shapes; @@ -42,15 +43,9 @@ namespace MapControl public static readonly DependencyProperty StrokeDashOffsetProperty = DependencyPropertyHelper.AddOwner(Shape.StrokeDashOffsetProperty); - public static readonly DependencyProperty StrokeDashCapProperty = + public static readonly DependencyProperty StrokeLineCapProperty = DependencyPropertyHelper.AddOwner(Shape.StrokeDashCapProperty); - public static readonly DependencyProperty StrokeStartLineCapProperty = - DependencyPropertyHelper.AddOwner(Shape.StrokeStartLineCapProperty); - - public static readonly DependencyProperty StrokeEndLineCapProperty = - DependencyPropertyHelper.AddOwner(Shape.StrokeEndLineCapProperty); - public static readonly DependencyProperty StrokeLineJoinProperty = DependencyPropertyHelper.AddOwner(Shape.StrokeLineJoinProperty); @@ -61,9 +56,10 @@ namespace MapControl { base.OnInitialized(e); - // If this.Stroke is not explicitly set, bind it to this.Foreground. - // - this.SetBindingOnUnsetProperty(StrokeProperty, this, ForegroundProperty, nameof(Foreground)); + if (Stroke == null) + { + SetBinding(StrokeProperty, this.CreateBinding(nameof(Foreground))); + } } public Pen CreatePen() @@ -74,9 +70,9 @@ namespace MapControl Thickness = StrokeThickness, LineJoin = StrokeLineJoin, MiterLimit = StrokeMiterLimit, - StartLineCap = StrokeStartLineCap, - EndLineCap = StrokeEndLineCap, - DashCap = StrokeDashCap, + StartLineCap = StrokeLineCap, + EndLineCap = StrokeLineCap, + DashCap = StrokeLineCap, DashStyle = new DashStyle(StrokeDashArray, StrokeDashOffset) }; } diff --git a/MapControl/WinUI/MapContentControl.WinUI.cs b/MapControl/WinUI/MapContentControl.WinUI.cs index 1e657a92..35f9104b 100644 --- a/MapControl/WinUI/MapContentControl.WinUI.cs +++ b/MapControl/WinUI/MapContentControl.WinUI.cs @@ -57,17 +57,20 @@ namespace MapControl if (parentMap != null) { - // If this.Background is not explicitly set, bind it to parentMap.Background. - // - this.SetBindingOnUnsetProperty(BackgroundProperty, parentMap, Panel.BackgroundProperty, nameof(Background)); + if (Background == null) + { + SetBinding(BackgroundProperty, parentMap.CreateBinding(nameof(Background))); + } - // If this.Foreground is not explicitly set, bind it to parentMap.Foreground. - // - this.SetBindingOnUnsetProperty(ForegroundProperty, parentMap, MapBase.ForegroundProperty, nameof(Foreground)); + if (Foreground == null) + { + SetBinding(ForegroundProperty, parentMap.CreateBinding(nameof(Foreground))); + } - // If this.BorderBrush is not explicitly set, bind it to parentMap.Foreground. - // - this.SetBindingOnUnsetProperty(BorderBrushProperty, parentMap, MapBase.ForegroundProperty, nameof(Foreground)); + if (BorderBrush == null) + { + SetBinding(BorderBrushProperty, parentMap.CreateBinding(nameof(Foreground))); + } } } } diff --git a/MapControl/WinUI/MapGraticule.WinUI.cs b/MapControl/WinUI/MapGraticule.WinUI.cs index 6e1af432..87aea3d6 100644 --- a/MapControl/WinUI/MapGraticule.WinUI.cs +++ b/MapControl/WinUI/MapGraticule.WinUI.cs @@ -32,11 +32,13 @@ namespace MapControl if (Children.Count == 0) { - path.SetBinding(Shape.StrokeProperty, this.GetOrCreateBinding(StrokeProperty, nameof(Stroke))); - path.SetBinding(Shape.StrokeThicknessProperty, this.GetOrCreateBinding(StrokeThicknessProperty, nameof(StrokeThickness))); - path.SetBinding(Shape.StrokeDashArrayProperty, this.GetOrCreateBinding(StrokeDashArrayProperty, nameof(StrokeDashArray))); - path.SetBinding(Shape.StrokeDashOffsetProperty, this.GetOrCreateBinding(StrokeDashOffsetProperty, nameof(StrokeDashOffset))); - path.SetBinding(Shape.StrokeDashCapProperty, this.GetOrCreateBinding(StrokeDashCapProperty, nameof(StrokeDashCap))); + path.SetBinding(Shape.StrokeProperty, this.CreateBinding(nameof(Stroke))); + path.SetBinding(Shape.StrokeThicknessProperty, this.CreateBinding(nameof(StrokeThickness))); + path.SetBinding(Shape.StrokeStartLineCapProperty, this.CreateBinding(nameof(StrokeLineCap))); + path.SetBinding(Shape.StrokeEndLineCapProperty, this.CreateBinding(nameof(StrokeLineCap))); + path.SetBinding(Shape.StrokeDashCapProperty, this.CreateBinding(nameof(StrokeLineCap))); + path.SetBinding(Shape.StrokeDashArrayProperty, this.CreateBinding(nameof(StrokeDashArray))); + path.SetBinding(Shape.StrokeDashOffsetProperty, this.CreateBinding(nameof(StrokeDashOffset))); Children.Add(path); } @@ -54,15 +56,15 @@ namespace MapControl else { textBlock = new TextBlock { RenderTransform = new MatrixTransform() }; - textBlock.SetBinding(TextBlock.FontSizeProperty, this.GetOrCreateBinding(FontSizeProperty, nameof(FontSize))); - textBlock.SetBinding(TextBlock.FontStyleProperty, this.GetOrCreateBinding(FontStyleProperty, nameof(FontStyle))); - textBlock.SetBinding(TextBlock.FontStretchProperty, this.GetOrCreateBinding(FontStretchProperty, nameof(FontStretch))); - textBlock.SetBinding(TextBlock.FontWeightProperty, this.GetOrCreateBinding(FontWeightProperty, nameof(FontWeight))); - textBlock.SetBinding(TextBlock.ForegroundProperty, this.GetOrCreateBinding(ForegroundProperty, nameof(Foreground))); + textBlock.SetBinding(TextBlock.FontSizeProperty, this.CreateBinding(nameof(FontSize))); + textBlock.SetBinding(TextBlock.FontStyleProperty, this.CreateBinding(nameof(FontStyle))); + textBlock.SetBinding(TextBlock.FontStretchProperty, this.CreateBinding(nameof(FontStretch))); + textBlock.SetBinding(TextBlock.FontWeightProperty, this.CreateBinding(nameof(FontWeight))); + textBlock.SetBinding(TextBlock.ForegroundProperty, this.CreateBinding(nameof(Foreground))); if (FontFamily != null) { - textBlock.SetBinding(TextBlock.FontFamilyProperty, this.GetOrCreateBinding(FontFamilyProperty, nameof(FontFamily))); + textBlock.SetBinding(TextBlock.FontFamilyProperty, this.CreateBinding(nameof(FontFamily))); } Children.Add(textBlock); diff --git a/MapControl/WinUI/MapItem.WinUI.cs b/MapControl/WinUI/MapItem.WinUI.cs index 055e05f3..cd1a041b 100644 --- a/MapControl/WinUI/MapItem.WinUI.cs +++ b/MapControl/WinUI/MapItem.WinUI.cs @@ -51,14 +51,20 @@ namespace MapControl if (parentMap != null) { - // If this.Background is not explicitly set, bind it to parentMap.Background - this.SetBindingOnUnsetProperty(BackgroundProperty, parentMap, Panel.BackgroundProperty, nameof(Background)); + if (Background == null) + { + SetBinding(BackgroundProperty, parentMap.CreateBinding(nameof(Background))); + } - // If this.Foreground is not explicitly set, bind it to parentMap.Foreground - this.SetBindingOnUnsetProperty(ForegroundProperty, parentMap, MapBase.ForegroundProperty, nameof(Foreground)); + if (Foreground == null) + { + SetBinding(ForegroundProperty, parentMap.CreateBinding(nameof(Foreground))); + } - // If this.BorderBrush is not explicitly set, bind it to parentMap.Foreground - this.SetBindingOnUnsetProperty(BorderBrushProperty, parentMap, MapBase.ForegroundProperty, nameof(Foreground)); + if (BorderBrush == null) + { + SetBinding(BorderBrushProperty, parentMap.CreateBinding(nameof(Foreground))); + } } } } diff --git a/MapControl/WinUI/MapOverlay.WinUI.cs b/MapControl/WinUI/MapOverlay.WinUI.cs index c46e8d4a..dc225263 100644 --- a/MapControl/WinUI/MapOverlay.WinUI.cs +++ b/MapControl/WinUI/MapOverlay.WinUI.cs @@ -5,10 +5,12 @@ using Windows.UI.Text; #if UWP using Windows.UI.Xaml; +using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Media; #else using Microsoft.UI.Text; using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Media; #endif @@ -46,14 +48,8 @@ namespace MapControl public static readonly DependencyProperty StrokeDashOffsetProperty = DependencyPropertyHelper.Register(nameof(StrokeDashOffset)); - public static readonly DependencyProperty StrokeDashCapProperty = - DependencyPropertyHelper.Register(nameof(StrokeDashCap), PenLineCap.Flat); - - public static readonly DependencyProperty StrokeStartLineCapProperty = - DependencyPropertyHelper.Register(nameof(StrokeStartLineCap), PenLineCap.Flat); - - public static readonly DependencyProperty StrokeEndLineCapProperty = - DependencyPropertyHelper.Register(nameof(StrokeEndLineCap), PenLineCap.Flat); + public static readonly DependencyProperty StrokeLineCapProperty = + DependencyPropertyHelper.Register(nameof(StrokeLineCap), PenLineCap.Flat); public static readonly DependencyProperty StrokeLineJoinProperty = DependencyPropertyHelper.Register(nameof(StrokeLineJoin), PenLineJoin.Miter); @@ -65,13 +61,15 @@ namespace MapControl { if (map != null) { - // If this.Forground is not explicitly set, bind it to map.Foreground. - // - this.SetBindingOnUnsetProperty(ForegroundProperty, map, MapBase.ForegroundProperty, nameof(Foreground)); + if (Foreground == null) + { + SetBinding(ForegroundProperty, map.CreateBinding(nameof(Foreground))); + } - // If this.Stroke is not explicitly set, bind it to this.Foreground. - // - this.SetBindingOnUnsetProperty(StrokeProperty, this, ForegroundProperty, nameof(Foreground)); + if (Stroke == null) + { + SetBinding(StrokeProperty, this.CreateBinding(nameof(Foreground))); + } } base.SetParentMap(map);