diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000000..9a0da4aa9b
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,17 @@
+This template is not intended to be prescriptive, but to help us review pull requests it would be useful if you included as much of the following information as possible:
+
+- What does the pull request do?
+- What is the current behavior?
+- What is the updated/expected behavior with this PR?
+- How was the solution implemented (if it's not obvious)?
+
+Checklist:
+
+- [ ] Added unit tests (if possible)?
+- [ ] Added XML documentation to any related classes?
+- [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Avaloniaui.net with user documentation
+
+If the pull request fixes issue(s) list them like this:
+
+Fixes #123
+Fixes #456
\ No newline at end of file
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);
}