diff --git a/src/Avalonia.Base/AttachedProperty.cs b/src/Avalonia.Base/AttachedProperty.cs index 9d4d40bfef..fdb04b6dfc 100644 --- a/src/Avalonia.Base/AttachedProperty.cs +++ b/src/Avalonia.Base/AttachedProperty.cs @@ -9,7 +9,7 @@ namespace Avalonia /// An attached avalonia property. /// /// The type of the property's value. - public class AttachedProperty : StyledPropertyBase + public class AttachedProperty : StyledProperty { /// /// Initializes a new instance of the class. @@ -35,11 +35,10 @@ namespace Avalonia /// /// The owner type. /// The property. - public StyledProperty AddOwner() where TOwner : IAvaloniaObject + public new AttachedProperty AddOwner() where TOwner : IAvaloniaObject { - var result = new StyledProperty(this, typeof(TOwner)); - AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result); - return result; + AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this); + return this; } } } diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index a46d567d28..4ab813333d 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -12,7 +12,6 @@ using Avalonia.Diagnostics; using Avalonia.Logging; using Avalonia.Threading; using Avalonia.Utilities; -using System.Reactive.Concurrency; namespace Avalonia { @@ -218,11 +217,6 @@ namespace Avalonia } else { - if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property)) - { - ThrowNotRegistered(property); - } - return GetValueInternal(property); } } @@ -377,11 +371,6 @@ namespace Avalonia { PriorityValue v; - if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property)) - { - ThrowNotRegistered(property); - } - if (!_values.TryGetValue(property, out v)) { v = CreatePriorityValue(property); @@ -804,11 +793,6 @@ namespace Avalonia var originalValue = value; - if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property)) - { - ThrowNotRegistered(property); - } - if (!TypeUtilities.TryConvertImplicit(property.PropertyType, value, out value)) { throw new ArgumentException(string.Format( @@ -836,18 +820,32 @@ namespace Avalonia } /// - /// Given a returns a registered avalonia property that is - /// equal or throws if not found. + /// Given a direct property, returns a registered avalonia property that is equivalent or + /// throws if not found. /// /// The property. /// The registered property. - public AvaloniaProperty GetRegistered(AvaloniaProperty property) + private AvaloniaProperty GetRegistered(AvaloniaProperty property) { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(this, property); + var direct = property as IDirectPropertyAccessor; + + if (direct == null) + { + throw new AvaloniaInternalException( + "AvaloniaObject.GetRegistered should only be called for direct properties"); + } + + if (property.OwnerType.IsAssignableFrom(GetType())) + { + return property; + } + + var result = AvaloniaPropertyRegistry.Instance.GetRegistered(this) + .FirstOrDefault(x => x == property); if (result == null) { - ThrowNotRegistered(property); + throw new ArgumentException($"Property '{property.Name} not registered on '{this.GetType()}"); } return result; @@ -898,15 +896,5 @@ namespace Avalonia value, priority); } - - /// - /// Throws an exception indicating that the specified property is not registered on this - /// object. - /// - /// The property - private void ThrowNotRegistered(AvaloniaProperty p) - { - throw new ArgumentException($"Property '{p.Name} not registered on '{this.GetType()}"); - } } } diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index fb78e3b2a0..f7dabd3a43 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -311,7 +311,9 @@ namespace Avalonia defaultBindingMode: defaultBindingMode); var result = new AttachedProperty(name, typeof(TOwner), metadata, inherits); - AvaloniaPropertyRegistry.Instance.Register(typeof(THost), result); + var registry = AvaloniaPropertyRegistry.Instance; + registry.Register(typeof(TOwner), result); + registry.RegisterAttached(typeof(THost), result); return result; } @@ -344,7 +346,9 @@ namespace Avalonia defaultBindingMode: defaultBindingMode); var result = new AttachedProperty(name, ownerType, metadata, inherits); - AvaloniaPropertyRegistry.Instance.Register(typeof(THost), result); + var registry = AvaloniaPropertyRegistry.Instance; + registry.Register(ownerType, result); + registry.RegisterAttached(typeof(THost), result); return result; } diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index ec1643427b..c0a4ace6ed 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; namespace Avalonia @@ -14,23 +13,14 @@ namespace Avalonia /// public class AvaloniaPropertyRegistry { - /// - /// The registered properties by type. - /// private readonly Dictionary> _registered = new Dictionary>(); - - /// - /// The registered properties by type cached values to increase performance. - /// - private readonly Dictionary> _registeredCache = - new Dictionary>(); - - /// - /// The registered attached properties by owner type. - /// private readonly Dictionary> _attached = new Dictionary>(); + private readonly Dictionary> _registeredCache = + new Dictionary>(); + private readonly Dictionary> _attachedCache = + new Dictionary>(); /// /// Gets the instance @@ -39,51 +29,68 @@ namespace Avalonia = new AvaloniaPropertyRegistry(); /// - /// Gets all attached s registered by an owner. + /// Gets all non-attached s registered on a type. /// - /// The owner type. + /// The type. /// A collection of definitions. - public IEnumerable GetAttached(Type ownerType) + public IEnumerable GetRegistered(Type type) { - Dictionary inner; + Contract.Requires(type != null); + + if (_registeredCache.TryGetValue(type, out var result)) + { + return result; + } - // Ensure the type's static ctor has been run. - RuntimeHelpers.RunClassConstructor(ownerType.TypeHandle); + var t = type; + result = new List(); - if (_attached.TryGetValue(ownerType, out inner)) + while (t != null) { - return inner.Values; + // Ensure the type's static ctor has been run. + RuntimeHelpers.RunClassConstructor(t.TypeHandle); + + if (_registered.TryGetValue(t, out var registered)) + { + result.AddRange(registered.Values); + } + + t = t.BaseType; } - return Enumerable.Empty(); + _registeredCache.Add(type, result); + return result; } /// - /// Gets all s registered on a type. + /// Gets all attached s registered on a type. /// /// The type. /// A collection of definitions. - public IEnumerable GetRegistered(Type type) + public IEnumerable GetRegisteredAttached(Type type) { Contract.Requires(type != null); - while (type != null) + if (_attachedCache.TryGetValue(type, out var result)) { - // Ensure the type's static ctor has been run. - RuntimeHelpers.RunClassConstructor(type.TypeHandle); + return result; + } - Dictionary inner; + var t = type; + result = new List(); - if (_registered.TryGetValue(type, out inner)) + while (t != null) + { + if (_attached.TryGetValue(t, out var attached)) { - foreach (var p in inner) - { - yield return p.Value; - } + result.AddRange(attached.Values); } - type = type.GetTypeInfo().BaseType; + t = t.BaseType; } + + _attachedCache.Add(type, result); + return result; } /// @@ -99,142 +106,92 @@ namespace Avalonia } /// - /// Finds a registered on a type. + /// Finds a registered non-attached property on a type by name. /// /// The type. - /// The property. - /// The registered property or null if not found. - /// - /// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a - /// different object but is equal according to . - /// - public AvaloniaProperty FindRegistered(Type type, AvaloniaProperty property) + /// The property name. + /// + /// The registered property or null if no matching property found. + /// + /// + /// The property name contains a '.'. + /// + public AvaloniaProperty FindRegistered(Type type, string name) { - Type currentType = type; - Dictionary cache; - AvaloniaProperty result; + Contract.Requires(type != null); + Contract.Requires(name != null); - if (_registeredCache.TryGetValue(type, out cache)) + if (name.Contains('.')) { - if (cache.TryGetValue(property.Id, out result)) - { - return result; - } + throw new InvalidOperationException("Attached properties not supported."); } - while (currentType != null) - { - Dictionary inner; - - if (_registered.TryGetValue(currentType, out inner)) - { - if (inner.TryGetValue(property.Id, out result)) - { - if (cache == null) - { - _registeredCache[type] = cache = new Dictionary(); - } - - cache[property.Id] = result; - - return result; - } - } - - currentType = currentType.GetTypeInfo().BaseType; - } - - return null; + return GetRegistered(type).FirstOrDefault(x => x.Name == name); } /// - /// Finds registered on an object. + /// Finds a registered non-attached property on a type by name. /// /// The object. - /// The property. - /// The registered property or null if not found. - /// - /// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a - /// different object but is equal according to . - /// - public AvaloniaProperty FindRegistered(object o, AvaloniaProperty property) + /// The property name. + /// + /// The registered property or null if no matching property found. + /// + /// + /// The property name contains a '.'. + /// + public AvaloniaProperty FindRegistered(AvaloniaObject o, string name) { - return FindRegistered(o.GetType(), property); + Contract.Requires(o != null); + Contract.Requires(name != null); + + return FindRegistered(o.GetType(), name); } /// - /// Finds a registered property on a type by name. + /// Finds a registered attached property on a type by name. /// /// The type. - /// - /// The property name. If an attached property it should be in the form - /// "OwnerType.PropertyName". - /// + /// The owner type. + /// The property name. /// /// The registered property or null if no matching property found. /// - public AvaloniaProperty FindRegistered(Type type, string name) + /// + /// The property name contains a '.'. + /// + public AvaloniaProperty FindRegisteredAttached(Type type, Type ownerType, string name) { Contract.Requires(type != null); + Contract.Requires(ownerType != null); Contract.Requires(name != null); - var parts = name.Split('.'); - var types = GetImplementedTypes(type).ToList(); - - if (parts.Length < 1 || parts.Length > 2) + if (name.Contains('.')) { - throw new ArgumentException("Invalid property name."); + throw new InvalidOperationException("Attached properties not supported."); } - string propertyName; - var results = GetRegistered(type); - - if (parts.Length == 1) - { - propertyName = parts[0]; - results = results.Where(x => !x.IsAttached || types.Contains(x.OwnerType.Name)); - } - else - { - if (!types.Contains(parts[0])) - { - results = results.Where(x => x.OwnerType.Name == parts[0]); - } - - propertyName = parts[1]; - } - - return results.FirstOrDefault(x => x.Name == propertyName); + return GetRegisteredAttached(type).FirstOrDefault(x => x.Name == name); } /// - /// Finds a registered property on an object by name. + /// Finds a registered non-attached property on a type by name. /// /// The object. - /// - /// The property name. If an attached property it should be in the form - /// "OwnerType.PropertyName". - /// + /// The owner type. + /// The property name. /// /// The registered property or null if no matching property found. /// - public AvaloniaProperty FindRegistered(AvaloniaObject o, string name) + /// + /// The property name contains a '.'. + /// + public AvaloniaProperty FindRegisteredAttached(AvaloniaObject o, Type ownerType, string name) { - return FindRegistered(o.GetType(), name); - } + Contract.Requires(o != null); + Contract.Requires(name != null); - /// - /// Returns a type and all its base types. - /// - /// The type. - /// The type and all its base types. - private IEnumerable GetImplementedTypes(Type type) - { - while (type != null) - { - yield return type.Name; - type = type.GetTypeInfo().BaseType; - } + return FindRegisteredAttached(o.GetType(), ownerType, name); } /// @@ -245,7 +202,11 @@ namespace Avalonia /// True if the property is registered, otherwise false. public bool IsRegistered(Type type, AvaloniaProperty property) { - return FindRegistered(type, property) != null; + Contract.Requires(type != null); + Contract.Requires(property != null); + + return Instance.GetRegistered(type).Any(x => x == property) || + Instance.GetRegisteredAttached(type).Any(x => x == property); } /// @@ -256,6 +217,9 @@ namespace Avalonia /// True if the property is registered, otherwise false. public bool IsRegistered(object o, AvaloniaProperty property) { + Contract.Requires(o != null); + Contract.Requires(property != null); + return IsRegistered(o.GetType(), property); } @@ -274,34 +238,53 @@ namespace Avalonia Contract.Requires(type != null); Contract.Requires(property != null); - Dictionary inner; - - if (!_registered.TryGetValue(type, out inner)) + if (!_registered.TryGetValue(type, out var inner)) { inner = new Dictionary(); + inner.Add(property.Id, property); _registered.Add(type, inner); } - - if (!inner.ContainsKey(property.Id)) + else if (!inner.ContainsKey(property.Id)) { inner.Add(property.Id, property); } + + _registeredCache.Clear(); + } - if (property.IsAttached) + /// + /// Registers an attached on a type. + /// + /// The type. + /// The property. + /// + /// You won't usually want to call this method directly, instead use the + /// + /// method. + /// + public void RegisterAttached(Type type, AvaloniaProperty property) + { + Contract.Requires(type != null); + Contract.Requires(property != null); + + if (!property.IsAttached) { - if (!_attached.TryGetValue(property.OwnerType, out inner)) - { - inner = new Dictionary(); - _attached.Add(property.OwnerType, inner); - } + throw new InvalidOperationException( + "Cannot register a non-attached property as attached."); + } - if (!inner.ContainsKey(property.Id)) - { - inner.Add(property.Id, property); - } + if (!_attached.TryGetValue(type, out var inner)) + { + inner = new Dictionary(); + inner.Add(property.Id, property); + _attached.Add(type, inner); + } + else + { + inner.Add(property.Id, property); } - _registeredCache.Clear(); + _attachedCache.Clear(); } } } \ No newline at end of file diff --git a/src/Avalonia.Base/DirectProperty.cs b/src/Avalonia.Base/DirectProperty.cs index 8352528285..1ce73c20ba 100644 --- a/src/Avalonia.Base/DirectProperty.cs +++ b/src/Avalonia.Base/DirectProperty.cs @@ -75,6 +75,9 @@ namespace Avalonia /// public Action Setter { get; } + /// + Type IDirectPropertyAccessor.Owner => typeof(TOwner); + /// /// Registers the direct property on another type. /// diff --git a/src/Avalonia.Base/IDirectPropertyAccessor.cs b/src/Avalonia.Base/IDirectPropertyAccessor.cs index 62aeef73c7..4f46652693 100644 --- a/src/Avalonia.Base/IDirectPropertyAccessor.cs +++ b/src/Avalonia.Base/IDirectPropertyAccessor.cs @@ -1,6 +1,8 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; + namespace Avalonia { /// @@ -14,6 +16,11 @@ namespace Avalonia /// bool IsReadOnly { get; } + /// + /// Gets the class that registered the property. + /// + Type Owner { get; } + /// /// Gets the value of the property on the instance. /// diff --git a/src/Avalonia.Controls/ColumnDefinitions.cs b/src/Avalonia.Controls/ColumnDefinitions.cs index 32fe5a6034..972655262a 100644 --- a/src/Avalonia.Controls/ColumnDefinitions.cs +++ b/src/Avalonia.Controls/ColumnDefinitions.cs @@ -27,7 +27,7 @@ namespace Avalonia.Controls public ColumnDefinitions(string s) : this() { - AddRange(GridLength.ParseLengths(s, CultureInfo.InvariantCulture).Select(x => new ColumnDefinition(x))); + AddRange(GridLength.ParseLengths(s).Select(x => new ColumnDefinition(x))); } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index fdb04f4ade..78dc994df7 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -19,7 +19,7 @@ namespace Avalonia.Controls { ContextMenuProperty.Changed.Subscribe(ContextMenuChanged); - MenuItem.ClickEvent.AddClassHandler(x => x.OnContextMenuClick, handledEventsToo: true); + MenuItem.ClickEvent.AddClassHandler(x => x.OnContextMenuClick, handledEventsToo: true); } /// @@ -75,13 +75,14 @@ namespace Avalonia.Controls { if (control != null) { - if(_popup == null) + if (_popup == null) { _popup = new Popup() { PlacementMode = PlacementMode.Pointer, PlacementTarget = control, - StaysOpen = false + StaysOpen = false, + ObeyScreenEdges = true }; _popup.Closed += PopupClosed; diff --git a/src/Avalonia.Controls/GridLength.cs b/src/Avalonia.Controls/GridLength.cs index 789953a249..608879812c 100644 --- a/src/Avalonia.Controls/GridLength.cs +++ b/src/Avalonia.Controls/GridLength.cs @@ -180,9 +180,8 @@ namespace Avalonia.Controls /// Parses a string to return a . /// /// The string. - /// The current culture. /// The . - public static GridLength Parse(string s, CultureInfo culture) + public static GridLength Parse(string s) { s = s.ToUpperInvariant(); @@ -193,12 +192,12 @@ namespace Avalonia.Controls else if (s.EndsWith("*")) { var valueString = s.Substring(0, s.Length - 1).Trim(); - var value = valueString.Length > 0 ? double.Parse(valueString, culture) : 1; + var value = valueString.Length > 0 ? double.Parse(valueString, CultureInfo.InvariantCulture) : 1; return new GridLength(value, GridUnitType.Star); } else { - var value = double.Parse(s, culture); + var value = double.Parse(s, CultureInfo.InvariantCulture); return new GridLength(value, GridUnitType.Pixel); } } @@ -207,15 +206,14 @@ namespace Avalonia.Controls /// Parses a string to return a collection of s. /// /// The string. - /// The current culture. /// The . - public static IEnumerable ParseLengths(string s, CultureInfo culture) + public static IEnumerable ParseLengths(string s) { - using (var tokenizer = new StringTokenizer(s, culture)) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture)) { while (tokenizer.TryReadString(out var item)) { - yield return Parse(item, culture); + yield return Parse(item); } } } diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index b8817d28f3..656f3890cd 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -40,6 +40,12 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty PlacementModeProperty = AvaloniaProperty.Register(nameof(PlacementMode), defaultValue: PlacementMode.Bottom); + /// + /// Defines the property. + /// + public static readonly StyledProperty ObeyScreenEdgesProperty = + AvaloniaProperty.Register(nameof(ObeyScreenEdges)); + /// /// Defines the property. /// @@ -136,6 +142,16 @@ namespace Avalonia.Controls.Primitives set { SetValue(PlacementModeProperty, value); } } + /// + /// Gets or sets a value indicating whether the popup positions itself within the nearest screen boundary + /// when its opened at a position where it would otherwise overlap the screen edge. + /// + public bool ObeyScreenEdges + { + get => GetValue(ObeyScreenEdgesProperty); + set => SetValue(ObeyScreenEdgesProperty, value); + } + /// /// Gets or sets the Horizontal offset of the popup in relation to the /// @@ -216,12 +232,12 @@ namespace Avalonia.Controls.Primitives var window = _topLevel as Window; if (window != null) { - window.Deactivated += WindowDeactivated; + window.Deactivated += WindowDeactivated; } else { var parentPopuproot = _topLevel as PopupRoot; - if(parentPopuproot != null && parentPopuproot.Parent!=null) + if (parentPopuproot != null && parentPopuproot.Parent != null) { ((Popup)(parentPopuproot.Parent)).Closed += ParentClosed; } @@ -234,13 +250,18 @@ namespace Avalonia.Controls.Primitives _popupRoot.Show(); + if (ObeyScreenEdges) + { + _popupRoot.SnapInsideScreenEdges(); + } + _ignoreIsOpenChanged = true; IsOpen = true; _ignoreIsOpenChanged = false; Opened?.Invoke(this, EventArgs.Empty); } - + /// /// Closes the popup. /// @@ -346,8 +367,10 @@ namespace Avalonia.Controls.Primitives /// The popup's position in screen coordinates. protected virtual Point GetPosition() { - return GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, + var result = GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, HorizontalOffset, VerticalOffset); + + return result; } internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset) @@ -399,8 +422,8 @@ namespace Avalonia.Controls.Primitives { if (!StaysOpen) { - if(!IsChildOrThis((IVisual)e.Source)) - { + if (!IsChildOrThis((IVisual)e.Source)) + { Close(); e.Handled = true; } @@ -412,12 +435,12 @@ namespace Avalonia.Controls.Primitives IVisual root = child.GetVisualRoot(); while (root is PopupRoot) { - if (root == PopupRoot) return true; + if (root == PopupRoot) return true; root = ((PopupRoot)root).Parent.GetVisualRoot(); } return false; } - + private void WindowDeactivated(object sender, EventArgs e) { if (!StaysOpen) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 507a085fed..457a7bd4b4 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -2,10 +2,12 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Linq; using Avalonia.Controls.Platform; using Avalonia.Controls.Presenters; using Avalonia.Interactivity; using Avalonia.Layout; +using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Styling; @@ -75,6 +77,30 @@ namespace Avalonia.Controls.Primitives /// public void Dispose() => PlatformImpl?.Dispose(); + /// + /// Moves the Popups position so that it doesnt overlap screen edges. + /// This method can be called immediately after Show has been called. + /// + public void SnapInsideScreenEdges() + { + var window = this.GetSelfAndLogicalAncestors().OfType().First(); + + var screen = window.Screens.ScreenFromPoint(Position); + + var screenX = Position.X + Bounds.Width - screen.Bounds.X; + var screenY = Position.Y + Bounds.Height - screen.Bounds.Y; + + if (screenX > screen.Bounds.Width) + { + Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width)); + } + + if (screenY > screen.Bounds.Height) + { + Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height)); + } + } + /// protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 77735f3f12..1a805a3822 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -207,7 +207,7 @@ namespace Avalonia.Controls.Primitives /// The control. /// The property value. /// - public bool GetIsTemplateFocusTarget(Control control) + public static bool GetIsTemplateFocusTarget(Control control) { return control.GetValue(IsTemplateFocusTargetProperty); } @@ -223,7 +223,7 @@ namespace Avalonia.Controls.Primitives /// attached property is set to true on an element in the control template, then the focus /// adorner will be shown around that control instead. /// - public void SetIsTemplateFocusTarget(Control control, bool value) + public static void SetIsTemplateFocusTarget(Control control, bool value) { control.SetValue(IsTemplateFocusTargetProperty, value); } diff --git a/src/Avalonia.Controls/RowDefinitions.cs b/src/Avalonia.Controls/RowDefinitions.cs index e677492580..2dfad7111a 100644 --- a/src/Avalonia.Controls/RowDefinitions.cs +++ b/src/Avalonia.Controls/RowDefinitions.cs @@ -27,7 +27,7 @@ namespace Avalonia.Controls public RowDefinitions(string s) : this() { - AddRange(GridLength.ParseLengths(s, CultureInfo.InvariantCulture).Select(x => new RowDefinition(x))); + AddRange(GridLength.ParseLengths(s).Select(x => new RowDefinition(x))); } } } \ No newline at end of file diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml index 66f226d2f6..efb31175fa 100644 --- a/src/Avalonia.Themes.Default/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/MenuItem.xaml @@ -45,7 +45,8 @@ + IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}" + ObeyScreenEdges="True"> @@ -92,7 +93,8 @@ + StaysOpen="True" + ObeyScreenEdges="True"> diff --git a/src/Avalonia.Visuals/CornerRadius.cs b/src/Avalonia.Visuals/CornerRadius.cs index db0ac97d49..33a553d477 100644 --- a/src/Avalonia.Visuals/CornerRadius.cs +++ b/src/Avalonia.Visuals/CornerRadius.cs @@ -4,6 +4,7 @@ using System; using System.Globalization; using System.Linq; +using Avalonia.Utilities; namespace Avalonia { @@ -53,31 +54,26 @@ namespace Avalonia return $"{TopLeft},{TopRight},{BottomRight},{BottomLeft}"; } - public static CornerRadius Parse(string s, CultureInfo culture) + public static CornerRadius Parse(string s) { - var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim()) - .ToList(); - - switch (parts.Count) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness")) { - case 1: - var uniform = double.Parse(parts[0], culture); - return new CornerRadius(uniform); - case 2: - var top = double.Parse(parts[0], culture); - var bottom = double.Parse(parts[1], culture); - return new CornerRadius(top, bottom); - case 4: - var topLeft = double.Parse(parts[0], culture); - var topRight = double.Parse(parts[1], culture); - var bottomRight = double.Parse(parts[2], culture); - var bottomLeft = double.Parse(parts[3], culture); - return new CornerRadius(topLeft, topRight, bottomRight, bottomLeft); - default: + if (tokenizer.TryReadDouble(out var a)) + { + if (tokenizer.TryReadDouble(out var b)) { - throw new FormatException("Invalid CornerRadius."); + if (tokenizer.TryReadDouble(out var c)) + { + return new CornerRadius(a, b, c, tokenizer.ReadDouble()); + } + + return new CornerRadius(a, b); } + + return new CornerRadius(a); + } + + throw new FormatException("Invalid CornerRadius."); } } @@ -85,7 +81,7 @@ namespace Avalonia { return cr1.TopLeft.Equals(cr2.TopLeft) && cr1.TopRight.Equals(cr2.TopRight) - && cr1.BottomRight.Equals(cr2.BottomRight) + && cr1.BottomRight.Equals(cr2.BottomRight) && cr1.BottomLeft.Equals(cr2.BottomLeft); } diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index 70804ee04f..b09ea2b68c 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -314,11 +314,10 @@ namespace Avalonia /// Parses a string. /// /// The string. - /// The current culture. /// The . - public static Matrix Parse(string s, CultureInfo culture) + public static Matrix Parse(string s) { - using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Matrix")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Matrix")) { return new Matrix( tokenizer.ReadDouble(), diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index d0c3edfeb2..62be3bf276 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/src/Avalonia.Visuals/Point.cs @@ -170,11 +170,10 @@ namespace Avalonia /// Parses a string. /// /// The string. - /// The current culture. /// The . - public static Point Parse(string s, CultureInfo culture) + public static Point Parse(string s) { - using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Point")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Point")) { return new Point( tokenizer.ReadDouble(), diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 748928ada3..73021ca29a 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -487,11 +487,10 @@ namespace Avalonia /// Parses a string. /// /// The string. - /// The current culture. /// The parsed . - public static Rect Parse(string s, CultureInfo culture) + public static Rect Parse(string s) { - using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Rect")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Rect")) { return new Rect( tokenizer.ReadDouble(), diff --git a/src/Avalonia.Visuals/RelativePoint.cs b/src/Avalonia.Visuals/RelativePoint.cs index a2ef0e6725..e4d7ea05cb 100644 --- a/src/Avalonia.Visuals/RelativePoint.cs +++ b/src/Avalonia.Visuals/RelativePoint.cs @@ -154,11 +154,10 @@ namespace Avalonia /// Parses a string. /// /// The string. - /// The current culture. /// The parsed . - public static RelativePoint Parse(string s, CultureInfo culture) + public static RelativePoint Parse(string s) { - using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid RelativePoint")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid RelativePoint")) { var x = tokenizer.ReadString(); var y = tokenizer.ReadString(); @@ -180,8 +179,8 @@ namespace Avalonia } return new RelativePoint( - double.Parse(x, culture) * scale, - double.Parse(y, culture) * scale, + double.Parse(x, CultureInfo.InvariantCulture) * scale, + double.Parse(y, CultureInfo.InvariantCulture) * scale, unit); } } diff --git a/src/Avalonia.Visuals/RelativeRect.cs b/src/Avalonia.Visuals/RelativeRect.cs index c13c3282db..ad42e30057 100644 --- a/src/Avalonia.Visuals/RelativeRect.cs +++ b/src/Avalonia.Visuals/RelativeRect.cs @@ -167,11 +167,10 @@ namespace Avalonia /// Parses a string. /// /// The string. - /// The current culture. /// The parsed . - public static RelativeRect Parse(string s, CultureInfo culture) + public static RelativeRect Parse(string s) { - using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid RelativeRect")) + using (var tokenizer = new StringTokenizer(s, exceptionMessage: "Invalid RelativeRect")) { var x = tokenizer.ReadString(); var y = tokenizer.ReadString(); @@ -202,10 +201,10 @@ namespace Avalonia } return new RelativeRect( - double.Parse(x, culture) * scale, - double.Parse(y, culture) * scale, - double.Parse(width, culture) * scale, - double.Parse(height, culture) * scale, + double.Parse(x, CultureInfo.InvariantCulture) * scale, + double.Parse(y, CultureInfo.InvariantCulture) * scale, + double.Parse(width, CultureInfo.InvariantCulture) * scale, + double.Parse(height, CultureInfo.InvariantCulture) * scale, unit); } } diff --git a/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs b/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs index 5dff3715b3..9cf849f59b 100644 --- a/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs @@ -41,12 +41,12 @@ namespace Avalonia.Rendering { add { + _tick += value; + if (_subscriberCount++ == 0) { Start(); } - - _tick += value; } remove diff --git a/src/Avalonia.Visuals/Size.cs b/src/Avalonia.Visuals/Size.cs index c5eaa33b41..b6889af6f0 100644 --- a/src/Avalonia.Visuals/Size.cs +++ b/src/Avalonia.Visuals/Size.cs @@ -150,11 +150,10 @@ namespace Avalonia /// Parses a string. /// /// The string. - /// The current culture. /// The . - public static Size Parse(string s, CultureInfo culture) + public static Size Parse(string s) { - using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Size")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Size")) { return new Size( tokenizer.ReadDouble(), diff --git a/src/Avalonia.Visuals/Thickness.cs b/src/Avalonia.Visuals/Thickness.cs index a5ca0a04a8..43a5fed9e7 100644 --- a/src/Avalonia.Visuals/Thickness.cs +++ b/src/Avalonia.Visuals/Thickness.cs @@ -165,25 +165,27 @@ namespace Avalonia /// Parses a string. /// /// The string. - /// The current culture. /// The . - public static Thickness Parse(string s, CultureInfo culture) + public static Thickness Parse(string s) { - using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Thickness")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness")) { - var a = tokenizer.ReadDouble(); - - if (tokenizer.TryReadDouble(out var b)) + if(tokenizer.TryReadDouble(out var a)) { - if (tokenizer.TryReadDouble(out var c)) + if (tokenizer.TryReadDouble(out var b)) { - return new Thickness(a, b, c, tokenizer.ReadDouble()); + if (tokenizer.TryReadDouble(out var c)) + { + return new Thickness(a, b, c, tokenizer.ReadDouble()); + } + + return new Thickness(a, b); } - return new Thickness(a, b); + return new Thickness(a); } - - return new Thickness(a); + + throw new FormatException("Invalid Thickness."); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs index a34ccaa413..bc3caff3b9 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs @@ -53,10 +53,7 @@ namespace Avalonia.Markup.Xaml.Converters } } - // First look for non-attached property on the type and then look for an attached property. - var property = AvaloniaPropertyRegistry.Instance.FindRegistered(type, s) ?? - AvaloniaPropertyRegistry.Instance.GetAttached(type) - .FirstOrDefault(x => x.Name == propertyName); + AvaloniaProperty property = AvaloniaPropertyRegistry.Instance.FindRegistered(type, propertyName); if (property == null) { diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs index 5da0efae1b..d8c1ecd4eb 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs @@ -13,7 +13,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return CornerRadius.Parse((string)value, culture); + return CornerRadius.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs index 1f72ca325c..05f3bed04d 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs @@ -18,7 +18,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return GridLength.Parse((string)value, culture); + return GridLength.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs index c477ff5637..fec6e31771 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs @@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return Matrix.Parse((string)value, culture); + return Matrix.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs index 1381fe7a75..9ca7212c68 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs @@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return Point.Parse((string)value, culture); + return Point.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs index b6c6da3055..29c7dbfd39 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs @@ -23,7 +23,7 @@ namespace Avalonia.Markup.Xaml.Converters var result = new List(pointStrs.Length); foreach (var pointStr in pointStrs) { - result.Add(Point.Parse(pointStr, culture)); + result.Add(Point.Parse(pointStr)); } return result; diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs index c9c6462f89..0946f95938 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs @@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return Rect.Parse((string)value, culture); + return Rect.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs index f68b8d66e6..b3178f1496 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs @@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return RelativePoint.Parse((string)value, culture); + return RelativePoint.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs index 64e39e224a..38c2833815 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs @@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return RelativeRect.Parse((string)value, culture); + return RelativeRect.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs index 73fef9ab1f..535e7948d5 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs @@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return Size.Parse((string)value, culture); + return Size.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs index 6ca5ec2f66..3a7652a153 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs @@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return Thickness.Parse((string)value, culture); + return Thickness.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs index bdb21abd77..fda5da902a 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs @@ -200,8 +200,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml var type = (getter ?? setter).DeclaringType; - var prop = AvaloniaPropertyRegistry.Instance.GetAttached(type) - .FirstOrDefault(v => v.Name == attachablePropertyName); + var prop = AvaloniaPropertyRegistry.Instance.FindRegistered(type, attachablePropertyName); if (prop != null) { diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs index 7de96ea220..59dbba7084 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs @@ -19,16 +19,36 @@ namespace Avalonia.Markup.Xaml.PortableXaml public class AvaloniaXamlType : XamlType { + static readonly AvaloniaPropertyTypeConverter propertyTypeConverter = new AvaloniaPropertyTypeConverter(); + public AvaloniaXamlType(Type underlyingType, XamlSchemaContext schemaContext) : base(underlyingType, schemaContext) { } + protected override XamlMember LookupAttachableMember(string name) + { + var m = base.LookupAttachableMember(name); + + if (m == null) + { + // Might be an AddOwnered attached property. + var avProp = AvaloniaPropertyRegistry.Instance.FindRegistered(UnderlyingType, name); + + if (avProp?.IsAttached == true) + { + return new AvaloniaPropertyXamlMember(avProp, this); + } + } + + return m; + } + protected override XamlMember LookupMember(string name, bool skipReadOnlyCheck) { var m = base.LookupMember(name, skipReadOnlyCheck); - if (m == null) + if (m == null && !name.Contains(".")) { //so far Portable.xaml haven't found the member/property //but what if we have AvaloniaProperty diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs b/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs index 90eabc69fb..ac64459dd7 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs +++ b/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Linq; using System.Reactive.Linq; using Avalonia.Data; @@ -15,9 +16,9 @@ namespace Avalonia.Markup.Data.Plugins /// public bool Match(object obj, string propertyName) { - if (obj is AvaloniaObject a) + if (obj is AvaloniaObject o) { - return AvaloniaPropertyRegistry.Instance.FindRegistered(a, propertyName) != null; + return LookupProperty(o, propertyName) != null; } return false; @@ -39,7 +40,7 @@ namespace Avalonia.Markup.Data.Plugins var instance = reference.Target; var o = (AvaloniaObject)instance; - var p = AvaloniaPropertyRegistry.Instance.FindRegistered(o, propertyName); + var p = LookupProperty(o, propertyName); if (p != null) { @@ -57,6 +58,54 @@ namespace Avalonia.Markup.Data.Plugins } } + private static AvaloniaProperty LookupProperty(AvaloniaObject o, string propertyName) + { + if (!propertyName.Contains(".")) + { + return AvaloniaPropertyRegistry.Instance.FindRegistered(o, propertyName); + } + else + { + var split = propertyName.Split('.'); + + if (split.Length == 2) + { + // HACK: We need a way to resolve types here using something like IXamlTypeResolver. + // We don't currently have that so we have to make our best guess. + var type = split[0]; + var name = split[1]; + var registry = AvaloniaPropertyRegistry.Instance; + var registered = registry.GetRegisteredAttached(o.GetType()) + .Concat(registry.GetRegistered(o.GetType())); + + foreach (var p in registered) + { + if (p.Name == name && IsOfType(p.OwnerType, type)) + { + return p; + } + } + } + } + + return null; + } + + private static bool IsOfType(Type type, string typeName) + { + while (type != null) + { + if (type.Name == typeName) + { + return true; + } + + type = type.BaseType; + } + + return false; + } + private class Accessor : PropertyAccessorBase { private readonly WeakReference _reference; diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index 113b2811dc..e1df24151d 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -2,16 +2,9 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Linq; -using Avalonia.Controls; using Avalonia.Platform; -using Avalonia.Utilities; using static Avalonia.Win32.Interop.UnmanagedMethods; -#if NETSTANDARD -using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception; -#endif - namespace Avalonia.Win32 { public class ScreenImpl : IScreenImpl diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 902abaf65b..95077f82a1 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -1,27 +1,23 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using Avalonia.Input.Platform; using System; using System.Collections.Generic; -using System.Reactive.Disposables; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Reactive.Disposables; using System.Runtime.InteropServices; using System.Threading; +using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Platform; -using Avalonia.Win32.Input; -using Avalonia.Win32.Interop; -using Avalonia.Controls; using Avalonia.Rendering; using Avalonia.Threading; -using System.IO; -#if NETSTANDARD -using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception; -#else -using System.ComponentModel; -#endif +using Avalonia.Win32.Input; +using Avalonia.Win32.Interop; namespace Avalonia { diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs new file mode 100644 index 0000000000..4e033be3fb --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs @@ -0,0 +1,52 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using Xunit; + +namespace Avalonia.Base.UnitTests +{ + public class AvaloniaObjectTests_AddOwner + { + [Fact] + public void AddOwnered_Property_Retains_Default_Value() + { + var target = new Class2(); + + Assert.Equal("foodefault", target.GetValue(Class2.FooProperty)); + } + + [Fact] + public void AddOwnered_Property_Does_Not_Retain_Validation() + { + var target = new Class2(); + + target.SetValue(Class2.FooProperty, "throw"); + } + + private class Class1 : AvaloniaObject + { + public static readonly StyledProperty FooProperty = + AvaloniaProperty.Register( + "Foo", + "foodefault", + validate: ValidateFoo); + + private static string ValidateFoo(AvaloniaObject arg1, string arg2) + { + if (arg2 == "throw") + { + throw new IndexOutOfRangeException(); + } + + return arg2; + } + } + + private class Class2 : AvaloniaObject + { + public static readonly StyledProperty FooProperty = + Class1.FooProperty.AddOwner(); + } + } +} diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs new file mode 100644 index 0000000000..ab2a2d899d --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs @@ -0,0 +1,52 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using Xunit; + +namespace Avalonia.Base.UnitTests +{ + public class AvaloniaObjectTests_Attached + { + [Fact] + public void AddOwnered_Property_Retains_Default_Value() + { + var target = new Class2(); + + Assert.Equal("foodefault", target.GetValue(Class2.FooProperty)); + } + + [Fact] + public void AddOwnered_Property_Retains_Validation() + { + var target = new Class2(); + + Assert.Throws(() => target.SetValue(Class2.FooProperty, "throw")); + } + + private class Class1 : AvaloniaObject + { + public static readonly AttachedProperty FooProperty = + AvaloniaProperty.RegisterAttached( + "Foo", + "foodefault", + validate: ValidateFoo); + + private static string ValidateFoo(AvaloniaObject arg1, string arg2) + { + if (arg2 == "throw") + { + throw new IndexOutOfRangeException(); + } + + return arg2; + } + } + + private class Class2 : AvaloniaObject + { + public static readonly AttachedProperty FooProperty = + Class1.FooProperty.AddOwner(); + } + } +} diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index c75150ca6d..80cd52d529 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -92,14 +92,13 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void Bind_Throws_Exception_For_Unregistered_Property() + public void Bind_Does_Not_Throw_Exception_For_Unregistered_Property() { Class1 target = new Class1(); - Assert.Throws(() => - { - target.Bind(Class2.BarProperty, Observable.Return("foo")); - }); + target.Bind(Class2.BarProperty, Observable.Never().StartWith("foo")); + + Assert.Equal("foo", target.GetValue(Class2.BarProperty)); } [Fact] diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs index 98f7289228..740023fd37 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs @@ -46,11 +46,11 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void GetValue_Throws_Exception_For_Unregistered_Property() + public void GetValue_Doesnt_Throw_Exception_For_Unregistered_Property() { var target = new Class3(); - Assert.Throws(() => target.GetValue(Class1.FooProperty)); + Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); } private class Class1 : AvaloniaObject diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs index 1f9c47f8ae..a56cd717b9 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs @@ -30,6 +30,16 @@ namespace Avalonia.Base.UnitTests Assert.Equal("newvalue", target.GetValue(Class1.FooProperty)); } + [Fact] + public void SetValue_Sets_Attached_Value() + { + Class2 target = new Class2(); + + target.SetValue(AttachedOwner.AttachedProperty, "newvalue"); + + Assert.Equal("newvalue", target.GetValue(AttachedOwner.AttachedProperty)); + } + [Fact] public void SetValue_Raises_PropertyChanged() { @@ -84,14 +94,27 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void SetValue_Throws_Exception_For_Unregistered_Property() + public void SetValue_Allows_Setting_Unregistered_Property() { Class1 target = new Class1(); - Assert.Throws(() => - { - target.SetValue(Class2.BarProperty, "invalid"); - }); + Assert.False(AvaloniaPropertyRegistry.Instance.IsRegistered(target, Class2.BarProperty)); + + target.SetValue(Class2.BarProperty, "bar"); + + Assert.Equal("bar", target.GetValue(Class2.BarProperty)); + } + + [Fact] + public void SetValue_Allows_Setting_Unregistered_Attached_Property() + { + Class1 target = new Class1(); + + Assert.False(AvaloniaPropertyRegistry.Instance.IsRegistered(target, AttachedOwner.AttachedProperty)); + + target.SetValue(AttachedOwner.AttachedProperty, "bar"); + + Assert.Equal("bar", target.GetValue(AttachedOwner.AttachedProperty)); } [Fact] @@ -189,6 +212,12 @@ namespace Avalonia.Base.UnitTests } } + private class AttachedOwner + { + public static readonly AttachedProperty AttachedProperty = + AvaloniaProperty.RegisterAttached("Attached"); + } + private class ImplictDouble { public ImplictDouble(double value) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs index da0b0252a3..8b2e500d37 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs @@ -4,12 +4,13 @@ using System.Linq; using System.Reactive.Linq; using Xunit; +using Xunit.Abstractions; namespace Avalonia.Base.UnitTests { public class AvaloniaPropertyRegistryTests { - public AvaloniaPropertyRegistryTests() + public AvaloniaPropertyRegistryTests(ITestOutputHelper s) { // Ensure properties are registered. AvaloniaProperty p; @@ -25,7 +26,7 @@ namespace Avalonia.Base.UnitTests .Select(x => x.Name) .ToArray(); - Assert.Equal(new[] { "Foo", "Baz", "Qux", "Attached" }, names); + Assert.Equal(new[] { "Foo", "Baz", "Qux" }, names); } [Fact] @@ -35,61 +36,41 @@ namespace Avalonia.Base.UnitTests .Select(x => x.Name) .ToArray(); - Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux", "Attached" }, names); + Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux" }, names); } [Fact] - public void GetAttached_Returns_Registered_Properties_For_Base_Types() + public void GetRegisteredAttached_Returns_Registered_Properties() { - string[] names = AvaloniaPropertyRegistry.Instance.GetAttached(typeof(AttachedOwner)).Select(x => x.Name).ToArray(); + string[] names = AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(typeof(Class1)) + .Select(x => x.Name) + .ToArray(); Assert.Equal(new[] { "Attached" }, names); } [Fact] - public void FindRegistered_Finds_Untyped_Property() + public void GetRegisteredAttached_Returns_Registered_Properties_For_Base_Types() { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Foo"); + string[] names = AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(typeof(Class2)) + .Select(x => x.Name) + .ToArray(); - Assert.Equal(Class1.FooProperty, result); + Assert.Equal(new[] { "Attached" }, names); } [Fact] - public void FindRegistered_Finds_Typed_Property() + public void FindRegistered_Finds_Property() { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Class1.Foo"); + var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Foo"); Assert.Equal(Class1.FooProperty, result); } [Fact] - public void FindRegistered_Finds_Typed_Inherited_Property() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Class1.Foo"); - - Assert.Equal(Class2.FooProperty, result); - } - - [Fact] - public void FindRegistered_Finds_Inherited_Property_With_Derived_Type_Name() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Class2.Foo"); - - Assert.Equal(Class2.FooProperty, result); - } - - [Fact] - public void FindRegistered_Finds_Attached_Property() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "AttachedOwner.Attached"); - - Assert.Equal(AttachedOwner.AttachedProperty, result); - } - - [Fact] - public void FindRegistered_Doesnt_Finds_Unqualified_Attached_Property() + public void FindRegistered_Doesnt_Find_Nonregistered_Property() { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Attached"); + var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Bar"); Assert.Null(result); } @@ -99,55 +80,34 @@ namespace Avalonia.Base.UnitTests { var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(AttachedOwner), "Attached"); - Assert.True(AttachedOwner.AttachedProperty == result); + Assert.Same(AttachedOwner.AttachedProperty, result); } [Fact] - public void FindRegistered_Finds_AddOwnered_Untyped_Attached_Property() + public void FindRegistered_Finds_AddOwnered_Attached_Property() { var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "Attached"); - Assert.True(AttachedOwner.AttachedProperty == result); + Assert.Same(AttachedOwner.AttachedProperty, result); } [Fact] - public void FindRegistered_Finds_AddOwnered_Typed_Attached_Property() + public void FindRegistered_Doesnt_Find_Non_AddOwnered_Attached_Property() { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "Class3.Attached"); - - Assert.True(AttachedOwner.AttachedProperty == result); - } - - [Fact] - public void FindRegistered_Finds_AddOwnered_AttachedTyped_Attached_Property() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "AttachedOwner.Attached"); - - Assert.True(AttachedOwner.AttachedProperty == result); - } - - [Fact] - public void FindRegistered_Finds_AddOwnered_BaseTyped_Attached_Property() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "Class1.Attached"); - - Assert.True(AttachedOwner.AttachedProperty == result); - } - - [Fact] - public void FindRegistered_Doesnt_Find_Nonregistered_Property() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Bar"); + var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Attached"); Assert.Null(result); } [Fact] - public void FindRegistered_Doesnt_Find_Nonregistered_Attached_Property() + public void FindRegisteredAttached_Finds_Property() { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class4), "AttachedOwner.Attached"); + var result = AvaloniaPropertyRegistry.Instance.FindRegisteredAttached( + typeof(Class1), + typeof(AttachedOwner), + "Attached"); - Assert.Null(result); + Assert.Equal(AttachedOwner.AttachedProperty, result); } private class Class1 : AvaloniaObject @@ -176,18 +136,18 @@ namespace Avalonia.Base.UnitTests private class Class3 : Class1 { - public static readonly StyledProperty AttachedProperty = + public static readonly AttachedProperty AttachedProperty = AttachedOwner.AttachedProperty.AddOwner(); } - public class Class4 : AvaloniaObject - { - } - private class AttachedOwner : Class1 { public static readonly AttachedProperty AttachedProperty = AvaloniaProperty.RegisterAttached("Attached"); } + + private class AttachedOwner2 : AttachedOwner + { + } } } diff --git a/tests/Avalonia.Controls.UnitTests/GridLengthTests.cs b/tests/Avalonia.Controls.UnitTests/GridLengthTests.cs index 0a811333d4..ab4da0ca7e 100644 --- a/tests/Avalonia.Controls.UnitTests/GridLengthTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridLengthTests.cs @@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Parse_Should_Parse_Auto() { - var result = GridLength.Parse("Auto", CultureInfo.InvariantCulture); + var result = GridLength.Parse("Auto"); Assert.Equal(GridLength.Auto, result); } @@ -21,7 +21,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Parse_Should_Parse_Auto_Lowercase() { - var result = GridLength.Parse("auto", CultureInfo.InvariantCulture); + var result = GridLength.Parse("auto"); Assert.Equal(GridLength.Auto, result); } @@ -29,7 +29,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Parse_Should_Parse_Star() { - var result = GridLength.Parse("*", CultureInfo.InvariantCulture); + var result = GridLength.Parse("*"); Assert.Equal(new GridLength(1, GridUnitType.Star), result); } @@ -37,7 +37,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Parse_Should_Parse_Star_Value() { - var result = GridLength.Parse("2*", CultureInfo.InvariantCulture); + var result = GridLength.Parse("2*"); Assert.Equal(new GridLength(2, GridUnitType.Star), result); } @@ -45,7 +45,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Parse_Should_Parse_Pixel_Value() { - var result = GridLength.Parse("2", CultureInfo.InvariantCulture); + var result = GridLength.Parse("2"); Assert.Equal(new GridLength(2, GridUnitType.Pixel), result); } @@ -53,13 +53,13 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Parse_Should_Throw_FormatException_For_Invalid_String() { - Assert.Throws(() => GridLength.Parse("2x", CultureInfo.InvariantCulture)); + Assert.Throws(() => GridLength.Parse("2x")); } [Fact] public void ParseLengths_Accepts_Comma_Separators() { - var result = GridLength.ParseLengths("*,Auto,2*,4", CultureInfo.InvariantCulture).ToList(); + var result = GridLength.ParseLengths("*,Auto,2*,4").ToList(); Assert.Equal( new[] @@ -75,7 +75,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void ParseLengths_Accepts_Space_Separators() { - var result = GridLength.ParseLengths("* Auto 2* 4", CultureInfo.InvariantCulture).ToList(); + var result = GridLength.ParseLengths("* Auto 2* 4").ToList(); Assert.Equal( new[] @@ -91,7 +91,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void ParseLengths_Accepts_Comma_Separators_With_Spaces() { - var result = GridLength.ParseLengths("*, Auto, 2* ,4", CultureInfo.InvariantCulture).ToList(); + var result = GridLength.ParseLengths("*, Auto, 2* ,4").ToList(); Assert.Equal( new[] diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs index f5c0c6ec15..f42e0daf2a 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs @@ -58,7 +58,7 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public async Task Should_Convert_Get_String_To_Double() { - var data = new Class1 { StringValue = "5.6" }; + var data = new Class1 { StringValue = $"{5.6}" }; var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double)); var result = await target.Take(1); @@ -94,12 +94,12 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public void Should_Convert_Set_String_To_Double() { - var data = new Class1 { StringValue = (5.6).ToString() }; + var data = new Class1 { StringValue = $"{5.6}" }; var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double)); target.OnNext(6.7); - Assert.Equal((6.7).ToString(), data.StringValue); + Assert.Equal($"{6.7}", data.StringValue); GC.KeepAlive(data); } @@ -111,7 +111,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string)); var result = await target.Take(1); - Assert.Equal((5.6).ToString(), result); + Assert.Equal($"{5.6}", result); GC.KeepAlive(data); } @@ -122,7 +122,7 @@ namespace Avalonia.Markup.UnitTests.Data var data = new Class1 { DoubleValue = 5.6 }; var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string)); - target.OnNext("6.7"); + target.OnNext($"{6.7}"); Assert.Equal(6.7, data.DoubleValue); @@ -318,15 +318,15 @@ namespace Avalonia.Markup.UnitTests.Data target.Subscribe(x => result.Add(x)); target.OnNext(1.2); - target.OnNext("3.4"); + target.OnNext($"{3.4}"); target.OnNext("bar"); Assert.Equal( new[] { - new BindingNotification("5.6"), - new BindingNotification("1.2"), - new BindingNotification("3.4"), + new BindingNotification($"{5.6}"), + new BindingNotification($"{1.2}"), + new BindingNotification($"{3.4}"), new BindingNotification( new InvalidCastException("'bar' is not a valid number."), BindingErrorType.Error) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AttachedPropertyOwner.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AttachedPropertyOwner.cs new file mode 100644 index 0000000000..aac5b01f96 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AttachedPropertyOwner.cs @@ -0,0 +1,14 @@ +using System; +using Avalonia.Controls; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml +{ + public class AttachedPropertyOwner + { + public static readonly AttachedProperty DoubleProperty = + AvaloniaProperty.RegisterAttached("Double"); + + public static double GetDouble(Control control) => control.GetValue(DoubleProperty); + public static void SetDouble(Control control, double value) => control.SetValue(DoubleProperty, value); + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index ba68838382..ac25b7ccbe 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -11,6 +11,7 @@ using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Styling; using Avalonia.UnitTests; +using Portable.Xaml; using System.Collections; using System.ComponentModel; using System.Linq; @@ -125,6 +126,24 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal("Foo", ToolTip.GetTip(target)); } + [Fact] + public void NonExistent_Property_Throws() + { + var xaml = + @""; + + Assert.Throws(() => AvaloniaXamlLoader.Parse(xaml)); + } + + [Fact] + public void Non_Attached_Property_With_Attached_Property_Syntax_Throws() + { + var xaml = + @""; + + Assert.Throws(() => AvaloniaXamlLoader.Parse(xaml)); + } + [Fact] public void ContentControl_ContentTemplate_Is_Functional() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs index a44d09dee7..568c6482f5 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs @@ -215,5 +215,71 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal("bar", textBlock.Text); } } + + [Fact] + public void Binding_To_Namespaced_Attached_Property_Works() + { + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) + { + var xaml = @" + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = (TextBlock)window.Content; + + window.DataContext = 5.6; + window.ApplyTemplate(); + + Assert.Equal(5.6, AttachedPropertyOwner.GetDouble(textBlock)); + } + } + + [Fact] + public void Binding_To_AddOwnered_Attached_Property_Works() + { + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) + { + var xaml = @" + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var testControl = (TestControl)window.Content; + + window.DataContext = 5.6; + window.ApplyTemplate(); + + Assert.Equal(5.6, testControl.Double); + } + } + + [Fact] + public void Binding_To_Attached_Property_Using_AddOwnered_Type_Works() + { + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) + { + var xaml = @" + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = (TextBlock)window.Content; + + window.DataContext = 5.6; + window.ApplyTemplate(); + + Assert.Equal(5.6, AttachedPropertyOwner.GetDouble(textBlock)); + } + } } } \ No newline at end of file diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestControl.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestControl.cs new file mode 100644 index 0000000000..d0591e5ee6 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestControl.cs @@ -0,0 +1,17 @@ +using System; +using Avalonia.Controls; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml +{ + public class TestControl : Control + { + public static readonly StyledProperty DoubleProperty = + AttachedPropertyOwner.DoubleProperty.AddOwner(); + + public double Double + { + get => GetValue(DoubleProperty); + set => SetValue(DoubleProperty, value); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs b/tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs index bc0bbdc867..56f9907409 100644 --- a/tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs @@ -11,7 +11,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Parses_Single_Uniform_Radius() { - var result = CornerRadius.Parse("3.4", CultureInfo.InvariantCulture); + var result = CornerRadius.Parse("3.4"); Assert.Equal(new CornerRadius(3.4), result); } @@ -19,7 +19,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Parses_Top_Bottom() { - var result = CornerRadius.Parse("1.1,2.2", CultureInfo.InvariantCulture); + var result = CornerRadius.Parse("1.1,2.2"); Assert.Equal(new CornerRadius(1.1, 2.2), result); } @@ -27,7 +27,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Parses_TopLeft_TopRight_BottomRight_BottomLeft() { - var result = CornerRadius.Parse("1.1,2.2,3.3,4.4", CultureInfo.InvariantCulture); + var result = CornerRadius.Parse("1.1,2.2,3.3,4.4"); Assert.Equal(new CornerRadius(1.1, 2.2, 3.3, 4.4), result); } @@ -35,7 +35,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Accepts_Spaces() { - var result = CornerRadius.Parse("1.1 2.2 3.3 4.4", CultureInfo.InvariantCulture); + var result = CornerRadius.Parse("1.1 2.2 3.3 4.4"); Assert.Equal(new CornerRadius(1.1, 2.2, 3.3, 4.4), result); } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs index 4c1e361952..ff1d17164e 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs @@ -8,7 +8,7 @@ namespace Avalonia.Visuals.UnitTests.Media [Fact] public void Parse_Parses() { - var matrix = Matrix.Parse("1,2,3,-4,5 6", CultureInfo.CurrentCulture); + var matrix = Matrix.Parse("1,2,3,-4,5 6"); var expected = new Matrix(1, 2, 3, -4, 5, 6); Assert.Equal(expected, matrix); } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs index 12070bfed3..cd0c7e1ace 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs @@ -8,7 +8,7 @@ namespace Avalonia.Visuals.UnitTests.Media [Fact] public void Parse_Parses() { - var rect = Rect.Parse("1,2 3,-4", CultureInfo.CurrentCulture); + var rect = Rect.Parse("1,2 3,-4"); var expected = new Rect(1, 2, 3, -4); Assert.Equal(expected, rect); } diff --git a/tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs b/tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs index f4a21fb6b4..b9eecc809f 100644 --- a/tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs @@ -11,7 +11,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Should_Accept_Absolute_Value() { - var result = RelativePoint.Parse("4,5", CultureInfo.InvariantCulture); + var result = RelativePoint.Parse("4,5"); Assert.Equal(new RelativePoint(4, 5, RelativeUnit.Absolute), result); } @@ -19,7 +19,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Should_Accept_Relative_Value() { - var result = RelativePoint.Parse("25%, 50%", CultureInfo.InvariantCulture); + var result = RelativePoint.Parse("25%, 50%"); Assert.Equal(new RelativePoint(0.25, 0.5, RelativeUnit.Relative), result); } diff --git a/tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs b/tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs index 9f25dcd413..68a0df2d9b 100644 --- a/tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs @@ -14,7 +14,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Should_Accept_Absolute_Value() { - var result = RelativeRect.Parse("4,5,50,60", CultureInfo.InvariantCulture); + var result = RelativeRect.Parse("4,5,50,60"); Assert.Equal(new RelativeRect(4, 5, 50, 60, RelativeUnit.Absolute), result, Compare); } @@ -22,7 +22,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Should_Accept_Relative_Value() { - var result = RelativeRect.Parse("10%, 20%, 40%, 70%", CultureInfo.InvariantCulture); + var result = RelativeRect.Parse("10%, 20%, 40%, 70%"); Assert.Equal(new RelativeRect(0.1, 0.2, 0.4, 0.7, RelativeUnit.Relative), result, Compare); } @@ -31,7 +31,7 @@ namespace Avalonia.Visuals.UnitTests public void Parse_Should_Throw_Mixed_Values() { Assert.Throws(() => - RelativeRect.Parse("10%, 20%, 40, 70%", CultureInfo.InvariantCulture)); + RelativeRect.Parse("10%, 20%, 40, 70%")); } } } diff --git a/tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs b/tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs index 03bf395d1e..ac4c6bc781 100644 --- a/tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs @@ -11,7 +11,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Parses_Single_Uniform_Size() { - var result = Thickness.Parse("1.2", CultureInfo.InvariantCulture); + var result = Thickness.Parse("1.2"); Assert.Equal(new Thickness(1.2), result); } @@ -19,7 +19,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Parses_Horizontal_Vertical() { - var result = Thickness.Parse("1.2,3.4", CultureInfo.InvariantCulture); + var result = Thickness.Parse("1.2,3.4"); Assert.Equal(new Thickness(1.2, 3.4), result); } @@ -27,7 +27,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Parses_Left_Top_Right_Bottom() { - var result = Thickness.Parse("1.2, 3.4, 5, 6", CultureInfo.InvariantCulture); + var result = Thickness.Parse("1.2, 3.4, 5, 6"); Assert.Equal(new Thickness(1.2, 3.4, 5, 6), result); } @@ -35,7 +35,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Accepts_Spaces() { - var result = Thickness.Parse("1.2 3.4 5 6", CultureInfo.InvariantCulture); + var result = Thickness.Parse("1.2 3.4 5 6"); Assert.Equal(new Thickness(1.2, 3.4, 5, 6), result); }