Browse Source

Merge branch 'master' into master

pull/1495/head
Steven Kirk 8 years ago
committed by GitHub
parent
commit
a5b535e4af
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      src/Avalonia.Base/AttachedProperty.cs
  2. 50
      src/Avalonia.Base/AvaloniaObject.cs
  3. 8
      src/Avalonia.Base/AvaloniaProperty.cs
  4. 283
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  5. 3
      src/Avalonia.Base/DirectProperty.cs
  6. 7
      src/Avalonia.Base/IDirectPropertyAccessor.cs
  7. 2
      src/Avalonia.Controls/ColumnDefinitions.cs
  8. 7
      src/Avalonia.Controls/ContextMenu.cs
  9. 14
      src/Avalonia.Controls/GridLength.cs
  10. 39
      src/Avalonia.Controls/Primitives/Popup.cs
  11. 26
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  12. 4
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  13. 2
      src/Avalonia.Controls/RowDefinitions.cs
  14. 6
      src/Avalonia.Themes.Default/MenuItem.xaml
  15. 40
      src/Avalonia.Visuals/CornerRadius.cs
  16. 5
      src/Avalonia.Visuals/Matrix.cs
  17. 5
      src/Avalonia.Visuals/Point.cs
  18. 5
      src/Avalonia.Visuals/Rect.cs
  19. 9
      src/Avalonia.Visuals/RelativePoint.cs
  20. 13
      src/Avalonia.Visuals/RelativeRect.cs
  21. 4
      src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs
  22. 5
      src/Avalonia.Visuals/Size.cs
  23. 24
      src/Avalonia.Visuals/Thickness.cs
  24. 5
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
  25. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs
  26. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs
  27. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs
  28. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs
  29. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs
  30. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs
  31. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs
  32. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs
  33. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs
  34. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs
  35. 3
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs
  36. 22
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs
  37. 55
      src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs
  38. 7
      src/Windows/Avalonia.Win32/ScreenImpl.cs
  39. 18
      src/Windows/Avalonia.Win32/Win32Platform.cs
  40. 52
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs
  41. 52
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs
  42. 9
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  43. 4
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs
  44. 39
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs
  45. 106
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs
  46. 18
      tests/Avalonia.Controls.UnitTests/GridLengthTests.cs
  47. 18
      tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs
  48. 14
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AttachedPropertyOwner.cs
  49. 19
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  50. 66
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs
  51. 17
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestControl.cs
  52. 8
      tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs
  53. 2
      tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs
  54. 2
      tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs
  55. 4
      tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs
  56. 6
      tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs
  57. 8
      tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs

9
src/Avalonia.Base/AttachedProperty.cs

@ -9,7 +9,7 @@ namespace Avalonia
/// An attached avalonia property.
/// </summary>
/// <typeparam name="TValue">The type of the property's value.</typeparam>
public class AttachedProperty<TValue> : StyledPropertyBase<TValue>
public class AttachedProperty<TValue> : StyledProperty<TValue>
{
/// <summary>
/// Initializes a new instance of the <see cref="AttachedProperty{TValue}"/> class.
@ -35,11 +35,10 @@ namespace Avalonia
/// </summary>
/// <typeparam name="TOwner">The owner type.</typeparam>
/// <returns>The property.</returns>
public StyledProperty<TValue> AddOwner<TOwner>() where TOwner : IAvaloniaObject
public new AttachedProperty<TValue> AddOwner<TOwner>() where TOwner : IAvaloniaObject
{
var result = new StyledProperty<TValue>(this, typeof(TOwner));
AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result);
return result;
AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this);
return this;
}
}
}

50
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
}
/// <summary>
/// Given a <see cref="AvaloniaProperty"/> 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.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The registered property.</returns>
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);
}
/// <summary>
/// Throws an exception indicating that the specified property is not registered on this
/// object.
/// </summary>
/// <param name="p">The property</param>
private void ThrowNotRegistered(AvaloniaProperty p)
{
throw new ArgumentException($"Property '{p.Name} not registered on '{this.GetType()}");
}
}
}

8
src/Avalonia.Base/AvaloniaProperty.cs

@ -311,7 +311,9 @@ namespace Avalonia
defaultBindingMode: defaultBindingMode);
var result = new AttachedProperty<TValue>(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<TValue>(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;
}

283
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
/// </summary>
public class AvaloniaPropertyRegistry
{
/// <summary>
/// The registered properties by type.
/// </summary>
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registered =
new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
/// <summary>
/// The registered properties by type cached values to increase performance.
/// </summary>
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registeredCache =
new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
/// <summary>
/// The registered attached properties by owner type.
/// </summary>
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _attached =
new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
private readonly Dictionary<Type, List<AvaloniaProperty>> _registeredCache =
new Dictionary<Type, List<AvaloniaProperty>>();
private readonly Dictionary<Type, List<AvaloniaProperty>> _attachedCache =
new Dictionary<Type, List<AvaloniaProperty>>();
/// <summary>
/// Gets the <see cref="AvaloniaPropertyRegistry"/> instance
@ -39,51 +29,68 @@ namespace Avalonia
= new AvaloniaPropertyRegistry();
/// <summary>
/// Gets all attached <see cref="AvaloniaProperty"/>s registered by an owner.
/// Gets all non-attached <see cref="AvaloniaProperty"/>s registered on a type.
/// </summary>
/// <param name="ownerType">The owner type.</param>
/// <param name="type">The type.</param>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
public IEnumerable<AvaloniaProperty> GetAttached(Type ownerType)
public IEnumerable<AvaloniaProperty> GetRegistered(Type type)
{
Dictionary<int, AvaloniaProperty> inner;
Contract.Requires<ArgumentNullException>(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<AvaloniaProperty>();
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<AvaloniaProperty>();
_registeredCache.Add(type, result);
return result;
}
/// <summary>
/// Gets all <see cref="AvaloniaProperty"/>s registered on a type.
/// Gets all attached <see cref="AvaloniaProperty"/>s registered on a type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
public IEnumerable<AvaloniaProperty> GetRegistered(Type type)
public IEnumerable<AvaloniaProperty> GetRegisteredAttached(Type type)
{
Contract.Requires<ArgumentNullException>(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<int, AvaloniaProperty> inner;
var t = type;
result = new List<AvaloniaProperty>();
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;
}
/// <summary>
@ -99,142 +106,92 @@ namespace Avalonia
}
/// <summary>
/// Finds a <see cref="AvaloniaProperty"/> registered on a type.
/// Finds a registered non-attached property on a type by name.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="property">The property.</param>
/// <returns>The registered property or null if not found.</returns>
/// <remarks>
/// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a
/// different object but is equal according to <see cref="object.Equals(object)"/>.
/// </remarks>
public AvaloniaProperty FindRegistered(Type type, AvaloniaProperty property)
/// <param name="name">The property name.</param>
/// <returns>
/// The registered property or null if no matching property found.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The property name contains a '.'.
/// </exception>
public AvaloniaProperty FindRegistered(Type type, string name)
{
Type currentType = type;
Dictionary<int, AvaloniaProperty> cache;
AvaloniaProperty result;
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(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<int, AvaloniaProperty> inner;
if (_registered.TryGetValue(currentType, out inner))
{
if (inner.TryGetValue(property.Id, out result))
{
if (cache == null)
{
_registeredCache[type] = cache = new Dictionary<int, AvaloniaProperty>();
}
cache[property.Id] = result;
return result;
}
}
currentType = currentType.GetTypeInfo().BaseType;
}
return null;
return GetRegistered(type).FirstOrDefault(x => x.Name == name);
}
/// <summary>
/// Finds <see cref="AvaloniaProperty"/> registered on an object.
/// Finds a registered non-attached property on a type by name.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="property">The property.</param>
/// <returns>The registered property or null if not found.</returns>
/// <remarks>
/// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a
/// different object but is equal according to <see cref="object.Equals(object)"/>.
/// </remarks>
public AvaloniaProperty FindRegistered(object o, AvaloniaProperty property)
/// <param name="name">The property name.</param>
/// <returns>
/// The registered property or null if no matching property found.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The property name contains a '.'.
/// </exception>
public AvaloniaProperty FindRegistered(AvaloniaObject o, string name)
{
return FindRegistered(o.GetType(), property);
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(name != null);
return FindRegistered(o.GetType(), name);
}
/// <summary>
/// Finds a registered property on a type by name.
/// Finds a registered attached property on a type by name.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="name">
/// The property name. If an attached property it should be in the form
/// "OwnerType.PropertyName".
/// </param>
/// <param name="ownerType">The owner type.</param>
/// <param name="name">The property name.</param>
/// <returns>
/// The registered property or null if no matching property found.
/// </returns>
public AvaloniaProperty FindRegistered(Type type, string name)
/// <exception cref="InvalidOperationException">
/// The property name contains a '.'.
/// </exception>
public AvaloniaProperty FindRegisteredAttached(Type type, Type ownerType, string name)
{
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(ownerType != null);
Contract.Requires<ArgumentNullException>(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);
}
/// <summary>
/// Finds a registered property on an object by name.
/// Finds a registered non-attached property on a type by name.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="name">
/// The property name. If an attached property it should be in the form
/// "OwnerType.PropertyName".
/// </param>
/// <param name="ownerType">The owner type.</param>
/// <param name="name">The property name.</param>
/// <returns>
/// The registered property or null if no matching property found.
/// </returns>
public AvaloniaProperty FindRegistered(AvaloniaObject o, string name)
/// <exception cref="InvalidOperationException">
/// The property name contains a '.'.
/// </exception>
public AvaloniaProperty FindRegisteredAttached(AvaloniaObject o, Type ownerType, string name)
{
return FindRegistered(o.GetType(), name);
}
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(name != null);
/// <summary>
/// Returns a type and all its base types.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The type and all its base types.</returns>
private IEnumerable<string> GetImplementedTypes(Type type)
{
while (type != null)
{
yield return type.Name;
type = type.GetTypeInfo().BaseType;
}
return FindRegisteredAttached(o.GetType(), ownerType, name);
}
/// <summary>
@ -245,7 +202,11 @@ namespace Avalonia
/// <returns>True if the property is registered, otherwise false.</returns>
public bool IsRegistered(Type type, AvaloniaProperty property)
{
return FindRegistered(type, property) != null;
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(property != null);
return Instance.GetRegistered(type).Any(x => x == property) ||
Instance.GetRegisteredAttached(type).Any(x => x == property);
}
/// <summary>
@ -256,6 +217,9 @@ namespace Avalonia
/// <returns>True if the property is registered, otherwise false.</returns>
public bool IsRegistered(object o, AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(property != null);
return IsRegistered(o.GetType(), property);
}
@ -274,34 +238,53 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(property != null);
Dictionary<int, AvaloniaProperty> inner;
if (!_registered.TryGetValue(type, out inner))
if (!_registered.TryGetValue(type, out var inner))
{
inner = new Dictionary<int, AvaloniaProperty>();
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)
/// <summary>
/// Registers an attached <see cref="AvaloniaProperty"/> on a type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="property">The property.</param>
/// <remarks>
/// You won't usually want to call this method directly, instead use the
/// <see cref="AvaloniaProperty.RegisterAttached{THost, TValue}(string, Type, TValue, bool, Data.BindingMode, Func{THost, TValue, TValue})"/>
/// method.
/// </remarks>
public void RegisterAttached(Type type, AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(property != null);
if (!property.IsAttached)
{
if (!_attached.TryGetValue(property.OwnerType, out inner))
{
inner = new Dictionary<int, AvaloniaProperty>();
_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<int, AvaloniaProperty>();
inner.Add(property.Id, property);
_attached.Add(type, inner);
}
else
{
inner.Add(property.Id, property);
}
_registeredCache.Clear();
_attachedCache.Clear();
}
}
}

3
src/Avalonia.Base/DirectProperty.cs

@ -75,6 +75,9 @@ namespace Avalonia
/// </summary>
public Action<TOwner, TValue> Setter { get; }
/// <inheritdoc/>
Type IDirectPropertyAccessor.Owner => typeof(TOwner);
/// <summary>
/// Registers the direct property on another type.
/// </summary>

7
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
{
/// <summary>
@ -14,6 +16,11 @@ namespace Avalonia
/// </summary>
bool IsReadOnly { get; }
/// <summary>
/// Gets the class that registered the property.
/// </summary>
Type Owner { get; }
/// <summary>
/// Gets the value of the property on the instance.
/// </summary>

2
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)));
}
}
}

7
src/Avalonia.Controls/ContextMenu.cs

@ -19,7 +19,7 @@ namespace Avalonia.Controls
{
ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);
MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);
}
/// <summary>
@ -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;

14
src/Avalonia.Controls/GridLength.cs

@ -180,9 +180,8 @@ namespace Avalonia.Controls
/// Parses a string to return a <see cref="GridLength"/>.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The <see cref="GridLength"/>.</returns>
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 <see cref="GridLength"/>s.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The <see cref="GridLength"/>.</returns>
public static IEnumerable<GridLength> ParseLengths(string s, CultureInfo culture)
public static IEnumerable<GridLength> 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);
}
}
}

39
src/Avalonia.Controls/Primitives/Popup.cs

@ -40,6 +40,12 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<PlacementMode> PlacementModeProperty =
AvaloniaProperty.Register<Popup, PlacementMode>(nameof(PlacementMode), defaultValue: PlacementMode.Bottom);
/// <summary>
/// Defines the <see cref="ObeyScreenEdges"/> property.
/// </summary>
public static readonly StyledProperty<bool> ObeyScreenEdgesProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges));
/// <summary>
/// Defines the <see cref="HorizontalOffset"/> property.
/// </summary>
@ -136,6 +142,16 @@ namespace Avalonia.Controls.Primitives
set { SetValue(PlacementModeProperty, value); }
}
/// <summary>
/// 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.
/// </summary>
public bool ObeyScreenEdges
{
get => GetValue(ObeyScreenEdgesProperty);
set => SetValue(ObeyScreenEdgesProperty, value);
}
/// <summary>
/// Gets or sets the Horizontal offset of the popup in relation to the <see cref="PlacementTarget"/>
/// </summary>
@ -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);
}
/// <summary>
/// Closes the popup.
/// </summary>
@ -346,8 +367,10 @@ namespace Avalonia.Controls.Primitives
/// <returns>The popup's position in screen coordinates.</returns>
protected virtual Point GetPosition()
{
return GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot,
var result = GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), 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)

26
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
/// <inheritdoc/>
public void Dispose() => PlatformImpl?.Dispose();
/// <summary>
/// Moves the Popups position so that it doesnt overlap screen edges.
/// This method can be called immediately after Show has been called.
/// </summary>
public void SnapInsideScreenEdges()
{
var window = this.GetSelfAndLogicalAncestors().OfType<Window>().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));
}
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{

4
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -207,7 +207,7 @@ namespace Avalonia.Controls.Primitives
/// <param name="control">The control.</param>
/// <returns>The property value.</returns>
/// <see cref="SetIsTemplateFocusTarget(Control, bool)"/>
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.
/// </remarks>
public void SetIsTemplateFocusTarget(Control control, bool value)
public static void SetIsTemplateFocusTarget(Control control, bool value)
{
control.SetValue(IsTemplateFocusTargetProperty, value);
}

2
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)));
}
}
}

6
src/Avalonia.Themes.Default/MenuItem.xaml

@ -45,7 +45,8 @@
<Popup Name="PART_Popup"
PlacementMode="Right"
StaysOpen="True"
IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}">
IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}"
ObeyScreenEdges="True">
<Border Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1">
@ -92,7 +93,8 @@
</ContentPresenter>
<Popup Name="PART_Popup"
IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}"
StaysOpen="True">
StaysOpen="True"
ObeyScreenEdges="True">
<Border Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1">

40
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);
}

5
src/Avalonia.Visuals/Matrix.cs

@ -314,11 +314,10 @@ namespace Avalonia
/// Parses a <see cref="Matrix"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The <see cref="Matrix"/>.</returns>
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(),

5
src/Avalonia.Visuals/Point.cs

@ -170,11 +170,10 @@ namespace Avalonia
/// Parses a <see cref="Point"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The <see cref="Thickness"/>.</returns>
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(),

5
src/Avalonia.Visuals/Rect.cs

@ -487,11 +487,10 @@ namespace Avalonia
/// Parses a <see cref="Rect"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The parsed <see cref="Rect"/>.</returns>
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(),

9
src/Avalonia.Visuals/RelativePoint.cs

@ -154,11 +154,10 @@ namespace Avalonia
/// Parses a <see cref="RelativePoint"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The parsed <see cref="RelativePoint"/>.</returns>
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);
}
}

13
src/Avalonia.Visuals/RelativeRect.cs

@ -167,11 +167,10 @@ namespace Avalonia
/// Parses a <see cref="RelativeRect"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The parsed <see cref="RelativeRect"/>.</returns>
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);
}
}

4
src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs

@ -41,12 +41,12 @@ namespace Avalonia.Rendering
{
add
{
_tick += value;
if (_subscriberCount++ == 0)
{
Start();
}
_tick += value;
}
remove

5
src/Avalonia.Visuals/Size.cs

@ -150,11 +150,10 @@ namespace Avalonia
/// Parses a <see cref="Size"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The <see cref="Size"/>.</returns>
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(),

24
src/Avalonia.Visuals/Thickness.cs

@ -165,25 +165,27 @@ namespace Avalonia
/// Parses a <see cref="Thickness"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The <see cref="Thickness"/>.</returns>
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.");
}
}

5
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)
{

2
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);
}
}
}

2
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);
}
}
}

2
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);
}
}
}

2
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);
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs

@ -23,7 +23,7 @@ namespace Avalonia.Markup.Xaml.Converters
var result = new List<Point>(pointStrs.Length);
foreach (var pointStr in pointStrs)
{
result.Add(Point.Parse(pointStr, culture));
result.Add(Point.Parse(pointStr));
}
return result;

2
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);
}
}
}

2
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);
}
}
}

2
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);
}
}
}

2
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);
}
}
}

2
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);
}
}
}

3
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)
{

22
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

55
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
/// <inheritdoc/>
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<AvaloniaObject> _reference;

7
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

18
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
{

52
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<string> FooProperty =
AvaloniaProperty.Register<Class1, string>(
"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<string> FooProperty =
Class1.FooProperty.AddOwner<Class2>();
}
}
}

52
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<IndexOutOfRangeException>(() => target.SetValue(Class2.FooProperty, "throw"));
}
private class Class1 : AvaloniaObject
{
public static readonly AttachedProperty<string> FooProperty =
AvaloniaProperty.RegisterAttached<Class1, Class2, string>(
"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<string> FooProperty =
Class1.FooProperty.AddOwner<Class2>();
}
}
}

9
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<ArgumentException>(() =>
{
target.Bind(Class2.BarProperty, Observable.Return("foo"));
});
target.Bind(Class2.BarProperty, Observable.Never<string>().StartWith("foo"));
Assert.Equal("foo", target.GetValue(Class2.BarProperty));
}
[Fact]

4
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<ArgumentException>(() => target.GetValue(Class1.FooProperty));
Assert.Equal("foodefault", target.GetValue(Class1.FooProperty));
}
private class Class1 : AvaloniaObject

39
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<ArgumentException>(() =>
{
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<string> AttachedProperty =
AvaloniaProperty.RegisterAttached<AttachedOwner, Class2, string>("Attached");
}
private class ImplictDouble
{
public ImplictDouble(double value)

106
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<string> AttachedProperty =
public static readonly AttachedProperty<string> AttachedProperty =
AttachedOwner.AttachedProperty.AddOwner<Class3>();
}
public class Class4 : AvaloniaObject
{
}
private class AttachedOwner : Class1
{
public static readonly AttachedProperty<string> AttachedProperty =
AvaloniaProperty.RegisterAttached<AttachedOwner, Class1, string>("Attached");
}
private class AttachedOwner2 : AttachedOwner
{
}
}
}

18
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<FormatException>(() => GridLength.Parse("2x", CultureInfo.InvariantCulture));
Assert.Throws<FormatException>(() => 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[]

18
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)

14
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<double> DoubleProperty =
AvaloniaProperty.RegisterAttached<AttachedPropertyOwner, Control, double>("Double");
public static double GetDouble(Control control) => control.GetValue(DoubleProperty);
public static void SetDouble(Control control, double value) => control.SetValue(DoubleProperty, value);
}
}

19
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 =
@"<ContentControl xmlns='https://github.com/avaloniaui' DoesntExist='foo'/>";
Assert.Throws<XamlObjectWriterException>(() => AvaloniaXamlLoader.Parse<ContentControl>(xaml));
}
[Fact]
public void Non_Attached_Property_With_Attached_Property_Syntax_Throws()
{
var xaml =
@"<ContentControl xmlns='https://github.com/avaloniaui' TextBlock.Text='foo'/>";
Assert.Throws<XamlObjectWriterException>(() => AvaloniaXamlLoader.Parse<ContentControl>(xaml));
}
[Fact]
public void ContentControl_ContentTemplate_Is_Functional()
{

66
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 = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<TextBlock local:AttachedPropertyOwner.Double='{Binding}'/>
</Window>";
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 = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<local:TestControl Double='{Binding}'/>
</Window>";
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 = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<TextBlock local:TestControl.Double='{Binding}'/>
</Window>";
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));
}
}
}
}

17
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<double> DoubleProperty =
AttachedPropertyOwner.DoubleProperty.AddOwner<TestControl>();
public double Double
{
get => GetValue(DoubleProperty);
set => SetValue(DoubleProperty, value);
}
}
}

8
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);
}

2
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);
}

2
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);
}

4
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);
}

6
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<FormatException>(() =>
RelativeRect.Parse("10%, 20%, 40, 70%", CultureInfo.InvariantCulture));
RelativeRect.Parse("10%, 20%, 40, 70%"));
}
}
}

8
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);
}

Loading…
Cancel
Save