Browse Source

Merge branch 'master' into fixes/2985-treeview-sort-crash

pull/3216/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
68f3424147
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      azure-pipelines.yml
  2. 4
      samples/ControlCatalog/App.xaml.cs
  3. 12
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  4. 17
      src/Avalonia.Base/Data/BindingOperations.cs
  5. 13
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  6. 6
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  7. 20
      src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
  8. 14
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  9. 25
      src/Avalonia.Base/Reactive/LightweightObservableBase.cs
  10. 2
      src/Avalonia.Base/Utilities/IdentifierParser.cs
  11. 21
      src/Avalonia.Controls/Application.cs
  12. 25
      src/Avalonia.Controls/Primitives/RangeBase.cs
  13. 6
      src/Avalonia.Controls/TopLevel.cs
  14. 13
      src/Avalonia.Styling/IDataContextProvider.cs
  15. 8
      src/Avalonia.Styling/IStyledElement.cs
  16. 2
      src/Avalonia.Styling/StyledElement.cs
  17. 27
      src/Avalonia.Visuals/Media/FontFamily.cs
  18. 34
      src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs
  19. 7
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  20. 11
      src/Markup/Avalonia.Markup/Data/Binding.cs
  21. 16
      src/Markup/Avalonia.Markup/Data/MultiBinding.cs
  22. 28
      tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs
  23. 59
      tests/Avalonia.Benchmarks/Data/BindingsBenchmark.cs
  24. 16
      tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs
  25. 18
      tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
  26. 30
      tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs
  27. 85
      tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs
  28. 2
      tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs
  29. 85
      tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs
  30. 3
      tests/Avalonia.Styling.UnitTests/SelectorTests_Name.cs
  31. 4
      tests/Avalonia.Styling.UnitTests/SelectorTests_Not.cs
  32. 5
      tests/Avalonia.Styling.UnitTests/SelectorTests_OfType.cs
  33. 9
      tests/Avalonia.Styling.UnitTests/SelectorTests_Or.cs
  34. 83
      tests/Avalonia.Styling.UnitTests/TestControlBase.cs
  35. 81
      tests/Avalonia.Styling.UnitTests/TestTemplatedControl.cs

12
azure-pipelines.yml

@ -34,9 +34,17 @@ jobs:
pool:
vmImage: 'macOS-10.14'
steps:
- task: DotNetCoreInstaller@0
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.0.x'
inputs:
version: '2.1.403'
packageType: sdk
version: 3.0.x
- task: UseDotNet@2
displayName: 'Use .NET Core Runtime 2.1.x'
inputs:
packageType: runtime
version: 2.1.x
- task: CmdLine@2
displayName: 'Install Mono 5.18'

4
samples/ControlCatalog/App.xaml.cs

@ -1,6 +1,4 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
@ -19,7 +17,7 @@ namespace ControlCatalog
desktopLifetime.MainWindow = new MainWindow();
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
singleViewLifetime.MainView = new MainView();
base.OnFrameworkInitializationCompleted();
}
}

12
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -173,12 +173,20 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(name != null);
if (name.Contains('.'))
if (name.Contains("."))
{
throw new InvalidOperationException("Attached properties not supported.");
}
return GetRegistered(type).FirstOrDefault(x => x.Name == name);
foreach (AvaloniaProperty x in GetRegistered(type))
{
if (x.Name == name)
{
return x;
}
}
return null;
}
/// <summary>

17
src/Avalonia.Base/Data/BindingOperations.cs

@ -2,7 +2,6 @@
// 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.Disposables;
using System.Reactive.Linq;
@ -56,22 +55,34 @@ namespace Avalonia.Data
if (source != null)
{
// Perf: Avoid allocating closure in the outer scope.
var targetCopy = target;
var propertyCopy = property;
var bindingCopy = binding;
return source
.Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue)
.Take(1)
.Subscribe(x => target.SetValue(property, x, binding.Priority));
.Subscribe(x => targetCopy.SetValue(propertyCopy, x, bindingCopy.Priority));
}
else
{
target.SetValue(property, binding.Value, binding.Priority);
return Disposable.Empty;
}
case BindingMode.OneWayToSource:
{
// Perf: Avoid allocating closure in the outer scope.
var bindingCopy = binding;
return Observable.CombineLatest(
binding.Observable,
target.GetObservable(property),
(_, v) => v)
.Subscribe(x => binding.Subject.OnNext(x));
.Subscribe(x => bindingCopy.Subject.OnNext(x));
}
default:
throw new ArgumentException("Invalid binding mode.");
}

13
src/Avalonia.Base/Data/Core/BindingExpression.cs

@ -21,6 +21,7 @@ namespace Avalonia.Data.Core
private readonly ExpressionObserver _inner;
private readonly Type _targetType;
private readonly object _fallbackValue;
private readonly object _targetNullValue;
private readonly BindingPriority _priority;
InnerListener _innerListener;
WeakReference<object> _value;
@ -51,7 +52,7 @@ namespace Avalonia.Data.Core
IValueConverter converter,
object converterParameter = null,
BindingPriority priority = BindingPriority.LocalValue)
: this(inner, targetType, AvaloniaProperty.UnsetValue, converter, converterParameter, priority)
: this(inner, targetType, AvaloniaProperty.UnsetValue, AvaloniaProperty.UnsetValue, converter, converterParameter, priority)
{
}
@ -63,6 +64,9 @@ namespace Avalonia.Data.Core
/// <param name="fallbackValue">
/// The value to use when the binding is unable to produce a value.
/// </param>
/// <param name="targetNullValue">
/// The value to use when the binding result is null.
/// </param>
/// <param name="converter">The value converter to use.</param>
/// <param name="converterParameter">
/// A parameter to pass to <paramref name="converter"/>.
@ -72,6 +76,7 @@ namespace Avalonia.Data.Core
ExpressionObserver inner,
Type targetType,
object fallbackValue,
object targetNullValue,
IValueConverter converter,
object converterParameter = null,
BindingPriority priority = BindingPriority.LocalValue)
@ -85,6 +90,7 @@ namespace Avalonia.Data.Core
Converter = converter;
ConverterParameter = converterParameter;
_fallbackValue = fallbackValue;
_targetNullValue = targetNullValue;
_priority = priority;
}
@ -196,6 +202,11 @@ namespace Avalonia.Data.Core
/// <inheritdoc/>
private object ConvertValue(object value)
{
if (value == null && _targetNullValue != AvaloniaProperty.UnsetValue)
{
return _targetNullValue;
}
if (value == BindingOperations.DoNothing)
{
return value;

6
src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@ -21,7 +21,7 @@ namespace Avalonia.Data.Core
/// An ordered collection of property accessor plugins that can be used to customize
/// the reading and subscription of property values on a type.
/// </summary>
public static readonly IList<IPropertyAccessorPlugin> PropertyAccessors =
public static readonly List<IPropertyAccessorPlugin> PropertyAccessors =
new List<IPropertyAccessorPlugin>
{
new AvaloniaPropertyAccessorPlugin(),
@ -33,7 +33,7 @@ namespace Avalonia.Data.Core
/// An ordered collection of validation checker plugins that can be used to customize
/// the validation of view model and model data.
/// </summary>
public static readonly IList<IDataValidationPlugin> DataValidators =
public static readonly List<IDataValidationPlugin> DataValidators =
new List<IDataValidationPlugin>
{
new DataAnnotationsValidationPlugin(),
@ -45,7 +45,7 @@ namespace Avalonia.Data.Core
/// An ordered collection of stream plugins that can be used to customize the behavior
/// of the '^' stream binding operator.
/// </summary>
public static readonly IList<IStreamPlugin> StreamHandlers =
public static readonly List<IStreamPlugin> StreamHandlers =
new List<IStreamPlugin>
{
new TaskStreamPlugin(),

20
src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs

@ -2,7 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Linq;
using System.Runtime.ExceptionServices;
namespace Avalonia.Data.Core.Plugins
{
@ -76,7 +76,7 @@ namespace Avalonia.Data.Core.Plugins
return false;
}
private class Accessor : PropertyAccessorBase
private class Accessor : PropertyAccessorBase, IObserver<object>
{
private readonly WeakReference<AvaloniaObject> _reference;
private readonly AvaloniaProperty _property;
@ -117,7 +117,7 @@ namespace Avalonia.Data.Core.Plugins
protected override void SubscribeCore()
{
_subscription = Instance?.GetObservable(_property).Subscribe(PublishValue);
_subscription = Instance?.GetObservable(_property).Subscribe(this);
}
protected override void UnsubscribeCore()
@ -125,6 +125,20 @@ namespace Avalonia.Data.Core.Plugins
_subscription?.Dispose();
_subscription = null;
}
void IObserver<object>.OnCompleted()
{
}
void IObserver<object>.OnError(Exception error)
{
ExceptionDispatchInfo.Capture(error).Throw();
}
void IObserver<object>.OnNext(object value)
{
PublishValue(value);
}
}
}
}

14
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@ -2,8 +2,6 @@
// 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.Core.Plugins;
namespace Avalonia.Data.Core
@ -41,7 +39,17 @@ namespace Avalonia.Data.Core
{
reference.TryGetTarget(out object target);
var plugin = ExpressionObserver.PropertyAccessors.FirstOrDefault(x => x.Match(target, PropertyName));
IPropertyAccessorPlugin plugin = null;
foreach (IPropertyAccessorPlugin x in ExpressionObserver.PropertyAccessors)
{
if (x.Match(target, PropertyName))
{
plugin = x;
break;
}
}
var accessor = plugin?.Start(reference, PropertyName);
if (_enableValidation && Next == null)

25
src/Avalonia.Base/Reactive/LightweightObservableBase.cs

@ -116,20 +116,33 @@ namespace Avalonia.Reactive
{
if (Volatile.Read(ref _observers) != null)
{
IObserver<T>[] observers;
IObserver<T>[] observers = null;
IObserver<T> singleObserver = null;
lock (this)
{
if (_observers == null)
{
return;
}
observers = _observers.ToArray();
if (_observers.Count == 1)
{
singleObserver = _observers[0];
}
else
{
observers = _observers.ToArray();
}
}
foreach (var observer in observers)
if (singleObserver != null)
{
observer.OnNext(value);
singleObserver.OnNext(value);
}
else
{
foreach (var observer in observers)
{
observer.OnNext(value);
}
}
}
}

2
src/Avalonia.Base/Utilities/IdentifierParser.cs

@ -15,7 +15,7 @@ namespace Avalonia.Utilities
{
if (IsValidIdentifierStart(r.Peek))
{
return r.TakeWhile(IsValidIdentifierChar);
return r.TakeWhile(c => IsValidIdentifierChar(c));
}
else
{

21
src/Avalonia.Controls/Application.cs

@ -32,7 +32,7 @@ namespace Avalonia
/// method.
/// - Tracks the lifetime of the application.
/// </remarks>
public class Application : AvaloniaObject, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode
public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode
{
/// <summary>
/// The application-global data templates.
@ -45,6 +45,12 @@ namespace Avalonia
private Styles _styles;
private IResourceDictionary _resources;
/// <summary>
/// Defines the <see cref="DataContext"/> property.
/// </summary>
public static readonly StyledProperty<object> DataContextProperty =
StyledElement.DataContextProperty.AddOwner<Application>();
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
@ -56,6 +62,19 @@ namespace Avalonia
Name = "Avalonia Application";
}
/// <summary>
/// Gets or sets the Applications's data context.
/// </summary>
/// <remarks>
/// The data context property specifies the default object that will
/// be used for data binding.
/// </remarks>
public object DataContext
{
get { return GetValue(DataContextProperty); }
set { SetValue(DataContextProperty, value); }
}
/// <summary>
/// Gets the current instance of the <see cref="Application"/> class.
/// </summary>

25
src/Avalonia.Controls/Primitives/RangeBase.cs

@ -75,7 +75,10 @@ namespace Avalonia.Controls.Primitives
set
{
ValidateDouble(value, "Minimum");
if (!ValidateDouble(value))
{
return;
}
if (IsInitialized)
{
@ -102,7 +105,10 @@ namespace Avalonia.Controls.Primitives
set
{
ValidateDouble(value, "Maximum");
if (!ValidateDouble(value))
{
return;
}
if (IsInitialized)
{
@ -129,7 +135,10 @@ namespace Avalonia.Controls.Primitives
set
{
ValidateDouble(value, "Value");
if (!ValidateDouble(value))
{
return;
}
if (IsInitialized)
{
@ -164,16 +173,12 @@ namespace Avalonia.Controls.Primitives
}
/// <summary>
/// Throws an exception if the double value is NaN or Inf.
/// Checks if the double value is not inifinity nor NaN.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="property">The name of the property being set.</param>
private static void ValidateDouble(double value, string property)
private static bool ValidateDouble(double value)
{
if (double.IsInfinity(value) || double.IsNaN(value))
{
throw new ArgumentException($"{value} is not a valid value for {property}.");
}
return !double.IsInfinity(value) || !double.IsNaN(value);
}
/// <summary>

6
src/Avalonia.Controls/TopLevel.cs

@ -269,6 +269,12 @@ namespace Avalonia.Controls
/// </summary>
protected virtual void HandleClosed()
{
var logicalArgs = new LogicalTreeAttachmentEventArgs(this);
((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs);
var visualArgs = new VisualTreeAttachmentEventArgs(this, this);
OnDetachedFromVisualTreeCore(visualArgs);
(this as IInputRoot).MouseDevice?.TopLevelClosed(this);
PlatformImpl = null;
OnClosed(EventArgs.Empty);

13
src/Avalonia.Styling/IDataContextProvider.cs

@ -0,0 +1,13 @@
namespace Avalonia
{
/// <summary>
/// Defines an element with a data context that can be used for binding.
/// </summary>
public interface IDataContextProvider : IAvaloniaObject
{
/// <summary>
/// Gets or sets the element's data context.
/// </summary>
object DataContext { get; set; }
}
}

8
src/Avalonia.Styling/IStyledElement.cs

@ -10,7 +10,8 @@ namespace Avalonia
IStyleHost,
ILogical,
IResourceProvider,
IResourceNode
IResourceNode,
IDataContextProvider
{
/// <summary>
/// Occurs when the control has finished initialization.
@ -27,11 +28,6 @@ namespace Avalonia
/// </summary>
new Classes Classes { get; set; }
/// <summary>
/// Gets or sets the control's data context.
/// </summary>
object DataContext { get; set; }
/// <summary>
/// Gets the control's logical parent.
/// </summary>

2
src/Avalonia.Styling/StyledElement.cs

@ -24,7 +24,7 @@ namespace Avalonia
/// - Implements <see cref="ILogical"/> to form part of a logical tree.
/// - A collection of class strings for custom styling.
/// </summary>
public class StyledElement : Animatable, IStyledElement, ISetLogicalParent, ISetInheritanceParent
public class StyledElement : Animatable, IDataContextProvider, IStyledElement, ISetLogicalParent, ISetInheritanceParent
{
/// <summary>
/// Defines the <see cref="DataContext"/> property.

27
src/Avalonia.Visuals/Media/FontFamily.cs

@ -184,36 +184,25 @@ namespace Avalonia.Media
{
unchecked
{
var hash = (int)2186146271;
if (Key != null)
{
hash = (hash * 15768619) ^ Key.GetHashCode();
}
else
{
hash = (hash * 15768619) ^ FamilyNames.GetHashCode();
}
if (Key != null)
{
hash = (hash * 15768619) ^ Key.GetHashCode();
}
return hash;
return ((FamilyNames != null ? FamilyNames.GetHashCode() : 0) * 397) ^ (Key != null ? Key.GetHashCode() : 0);
}
}
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj))
{
return true;
}
if (!(obj is FontFamily other))
{
return false;
}
if (Key != null)
if (!Equals(Key, other.Key))
{
return other.FamilyNames.Equals(FamilyNames) && other.Key.Equals(Key);
return false;
}
return other.FamilyNames.Equals(FamilyNames);

34
src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs

@ -111,7 +111,24 @@ namespace Avalonia.Media.Fonts
/// </returns>
public override int GetHashCode()
{
return ToString().GetHashCode();
if (Count == 0)
{
return 0;
}
unchecked
{
int hash = 17;
for (var i = 0; i < Names.Count; i++)
{
string name = Names[i];
hash = hash * 23 + name.GetHashCode();
}
return hash;
}
}
/// <summary>
@ -128,7 +145,20 @@ namespace Avalonia.Media.Fonts
return false;
}
return other.ToString().Equals(ToString());
if (other.Count != Count)
{
return false;
}
for (int i = 0; i < Count; i++)
{
if (Names[i] != other.Names[i])
{
return false;
}
}
return true;
}
public int Count => Names.Count;

7
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -52,6 +52,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
// the context.
object anchor = context.GetFirstParent<IControl>();
if(anchor is null)
{
// Try to find IDataContextProvider, this was added to allow us to find
// a datacontext for Application class when using NativeMenuItems.
anchor = context.GetFirstParent<IDataContextProvider>();
}
// If a control was not found, then try to find the highest-level style as the XAML
// file could be a XAML file containing only styles.
return anchor ??

11
src/Markup/Avalonia.Markup/Data/Binding.cs

@ -26,6 +26,7 @@ namespace Avalonia.Data
public Binding()
{
FallbackValue = AvaloniaProperty.UnsetValue;
TargetNullValue = AvaloniaProperty.UnsetValue;
}
/// <summary>
@ -60,6 +61,11 @@ namespace Avalonia.Data
/// </summary>
public object FallbackValue { get; set; }
/// <summary>
/// Gets or sets the value to use when the binding result is null.
/// </summary>
public object TargetNullValue { get; set; }
/// <summary>
/// Gets or sets the binding mode.
/// </summary>
@ -209,6 +215,7 @@ namespace Avalonia.Data
observer,
targetType,
fallback,
TargetNullValue,
converter ?? DefaultValueConverter.Instance,
ConverterParameter,
Priority);
@ -224,9 +231,9 @@ namespace Avalonia.Data
{
Contract.Requires<ArgumentNullException>(target != null);
if (!(target is IStyledElement))
if (!(target is IDataContextProvider))
{
target = anchor as IStyledElement;
target = anchor as IDataContextProvider;
if (target == null)
{

16
src/Markup/Avalonia.Markup/Data/MultiBinding.cs

@ -37,6 +37,11 @@ namespace Avalonia.Data
/// </summary>
public object FallbackValue { get; set; }
/// <summary>
/// Gets or sets the value to use when the binding result is null.
/// </summary>
public object TargetNullValue { get; set; }
/// <summary>
/// Gets or sets the binding mode.
/// </summary>
@ -57,6 +62,12 @@ namespace Avalonia.Data
/// </summary>
public string StringFormat { get; set; }
public MultiBinding()
{
FallbackValue = AvaloniaProperty.UnsetValue;
TargetNullValue = AvaloniaProperty.UnsetValue;
}
/// <inheritdoc/>
public InstancedBinding Initiate(
IAvaloniaObject target,
@ -102,6 +113,11 @@ namespace Avalonia.Data
var culture = CultureInfo.CurrentCulture;
var converted = converter.Convert(values, targetType, ConverterParameter, culture);
if (converted == null)
{
converted = TargetNullValue;
}
if (converted == AvaloniaProperty.UnsetValue)
{
converted = FallbackValue;

28
tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs

@ -139,6 +139,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
ExpressionObserver.Create(data, o => o.StringValue),
typeof(int),
42,
AvaloniaProperty.UnsetValue,
DefaultValueConverter.Instance);
var result = await target.Take(1);
@ -160,6 +161,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
ExpressionObserver.Create(data, o => o.StringValue, true),
typeof(int),
42,
AvaloniaProperty.UnsetValue,
DefaultValueConverter.Instance);
var result = await target.Take(1);
@ -181,6 +183,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
ExpressionObserver.Create(data, o => o.StringValue),
typeof(int),
"bar",
AvaloniaProperty.UnsetValue,
DefaultValueConverter.Instance);
var result = await target.Take(1);
@ -203,6 +206,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
ExpressionObserver.Create(data, o => o.StringValue, true),
typeof(int),
"bar",
AvaloniaProperty.UnsetValue,
DefaultValueConverter.Instance);
var result = await target.Take(1);
@ -238,6 +242,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
ExpressionObserver.Create(data, o => o.DoubleValue),
typeof(string),
"9.8",
AvaloniaProperty.UnsetValue,
DefaultValueConverter.Instance);
target.OnNext("foo");
@ -353,6 +358,29 @@ namespace Avalonia.Base.UnitTests.Data.Core
GC.KeepAlive(data);
}
[Fact]
public async Task Null_Value_Should_Use_TargetNullValue()
{
var data = new Class1 { StringValue = "foo" };
var target = new BindingExpression(
ExpressionObserver.Create(data, o => o.StringValue),
typeof(string),
AvaloniaProperty.UnsetValue,
"bar",
DefaultValueConverter.Instance);
object result = null;
target.Subscribe(x => result = x);
Assert.Equal("foo", result);
data.StringValue = null;
Assert.Equal("bar", result);
GC.KeepAlive(data);
}
private class Class1 : NotifyingBase
{
private string _stringValue;

59
tests/Avalonia.Benchmarks/Data/BindingsBenchmark.cs

@ -0,0 +1,59 @@
using Avalonia.Data;
using BenchmarkDotNet.Attributes;
namespace Avalonia.Benchmarks.Data
{
[MemoryDiagnoser, InProcess]
public class BindingsBenchmark
{
[Benchmark]
public void TwoWayBinding_Via_Binding()
{
var instance = new TestClass();
var binding = new Binding(nameof(TestClass.BoundValue), BindingMode.TwoWay)
{
Source = instance
};
instance.Bind(TestClass.IntValueProperty, binding);
}
[Benchmark]
public void UpdateTwoWayBinding_Via_Binding()
{
var instance = new TestClass();
var binding = new Binding(nameof(TestClass.BoundValue), BindingMode.TwoWay)
{
Source = instance
};
instance.Bind(TestClass.IntValueProperty, binding);
for (int i = 0; i < 60; i++)
{
instance.IntValue = i;
}
}
private class TestClass : AvaloniaObject
{
public static readonly StyledProperty<int> IntValueProperty =
AvaloniaProperty.Register<TestClass, int>(nameof(IntValue));
public static readonly StyledProperty<int> BoundValueProperty =
AvaloniaProperty.Register<TestClass, int>(nameof(BoundValue));
public int IntValue
{
get => GetValue(IntValueProperty);
set => SetValue(IntValueProperty, value);
}
public int BoundValue
{
get => GetValue(BoundValueProperty);
set => SetValue(BoundValueProperty, value);
}
}
}
}

16
tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs

@ -82,22 +82,6 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(50, target.Value);
}
[Fact]
public void Properties_Should_Not_Accept_Nan_And_Inifinity()
{
var target = new TestRange();
Assert.Throws<ArgumentException>(() => target.Minimum = double.NaN);
Assert.Throws<ArgumentException>(() => target.Minimum = double.PositiveInfinity);
Assert.Throws<ArgumentException>(() => target.Minimum = double.NegativeInfinity);
Assert.Throws<ArgumentException>(() => target.Maximum = double.NaN);
Assert.Throws<ArgumentException>(() => target.Maximum = double.PositiveInfinity);
Assert.Throws<ArgumentException>(() => target.Maximum = double.NegativeInfinity);
Assert.Throws<ArgumentException>(() => target.Value = double.NaN);
Assert.Throws<ArgumentException>(() => target.Value = double.PositiveInfinity);
Assert.Throws<ArgumentException>(() => target.Value = double.NegativeInfinity);
}
[Theory]
[InlineData(true)]
[InlineData(false)]

18
tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs

@ -405,6 +405,24 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal(42, target.Value);
}
[Fact]
public void Should_Return_TargetNullValue_When_Value_Is_Null()
{
var target = new TextBlock();
var source = new Source { Foo = null };
var binding = new Binding
{
Source = source,
Path = "Foo",
TargetNullValue = "(null)",
};
target.Bind(TextBlock.TextProperty, binding);
Assert.Equal("(null)", target.Text);
}
[Fact]
public void Null_Path_Should_Bind_To_DataContext()
{

30
tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs

@ -94,6 +94,28 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal("fallback", target.Text);
}
[Fact]
public void Should_Return_TargetNullValue_When_Value_Is_Null()
{
var target = new TextBlock();
var binding = new MultiBinding
{
Converter = new NullValueConverter(),
Bindings = new[]
{
new Binding { Path = "A" },
new Binding { Path = "B" },
new Binding { Path = "C" },
},
TargetNullValue = "(null)",
};
target.Bind(TextBlock.TextProperty, binding);
Assert.Equal("(null)", target.Text);
}
private class ConcatConverter : IMultiValueConverter
{
public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
@ -109,5 +131,13 @@ namespace Avalonia.Markup.UnitTests.Data
return AvaloniaProperty.UnsetValue;
}
}
private class NullValueConverter : IMultiValueConverter
{
public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
}
}

85
tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs

@ -81,89 +81,12 @@ namespace Avalonia.Styling.UnitTests
Assert.Equal("TestLogical1 > TestLogical3", selector.ToString());
}
public abstract class TestLogical : ILogical, IStyleable
public abstract class TestLogical : Control
{
public TestLogical()
public ILogical LogicalParent
{
Classes = new Classes();
}
public event EventHandler<AvaloniaPropertyChangedEventArgs> PropertyChanged;
public event EventHandler<AvaloniaPropertyChangedEventArgs> InheritablePropertyChanged;
public event EventHandler<LogicalTreeAttachmentEventArgs> AttachedToLogicalTree;
public event EventHandler<LogicalTreeAttachmentEventArgs> DetachedFromLogicalTree;
public Classes Classes { get; }
public string Name { get; set; }
public bool IsAttachedToLogicalTree { get; }
public IAvaloniaReadOnlyList<ILogical> LogicalChildren { get; set; }
public ILogical LogicalParent { get; set; }
public Type StyleKey { get; }
public ITemplatedControl TemplatedParent { get; }
IObservable<IStyleable> IStyleable.StyleDetach { get; }
IAvaloniaReadOnlyList<string> IStyleable.Classes => Classes;
public object GetValue(AvaloniaProperty property)
{
throw new NotImplementedException();
}
public T GetValue<T>(AvaloniaProperty<T> property)
{
throw new NotImplementedException();
}
public void SetValue(AvaloniaProperty property, object value, BindingPriority priority)
{
throw new NotImplementedException();
}
public void SetValue<T>(AvaloniaProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public IDisposable Bind(AvaloniaProperty property, IObservable<object> source, BindingPriority priority)
{
throw new NotImplementedException();
}
public bool IsAnimating(AvaloniaProperty property)
{
throw new NotImplementedException();
}
public bool IsSet(AvaloniaProperty property)
{
throw new NotImplementedException();
}
public IDisposable Bind<T>(AvaloniaProperty<T> property, IObservable<T> source, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public void NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
throw new NotImplementedException();
}
public void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
throw new NotImplementedException();
}
public void NotifyResourcesChanged(ResourcesChangedEventArgs e)
{
throw new NotImplementedException();
get => Parent;
set => ((ISetLogicalParent)this).SetParent(value);
}
}

2
tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs

@ -144,7 +144,7 @@ namespace Avalonia.Styling.UnitTests
Assert.Equal(new[] { true, false }, result);
}
public class Control1 : TestControlBase
public class Control1 : Control
{
}
}

85
tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs

@ -111,89 +111,12 @@ namespace Avalonia.Styling.UnitTests
Assert.Equal("TestLogical1.foo TestLogical3", selector.ToString());
}
public abstract class TestLogical : ILogical, IStyleable
public abstract class TestLogical : Control
{
public TestLogical()
public ILogical LogicalParent
{
Classes = new Classes();
}
public event EventHandler<AvaloniaPropertyChangedEventArgs> PropertyChanged;
public event EventHandler<AvaloniaPropertyChangedEventArgs> InheritablePropertyChanged;
public event EventHandler<LogicalTreeAttachmentEventArgs> AttachedToLogicalTree;
public event EventHandler<LogicalTreeAttachmentEventArgs> DetachedFromLogicalTree;
public Classes Classes { get; }
public string Name { get; set; }
public bool IsAttachedToLogicalTree { get; }
public IAvaloniaReadOnlyList<ILogical> LogicalChildren { get; set; }
public ILogical LogicalParent { get; set; }
public Type StyleKey { get; }
public ITemplatedControl TemplatedParent { get; }
IAvaloniaReadOnlyList<string> IStyleable.Classes => Classes;
IObservable<IStyleable> IStyleable.StyleDetach { get; }
public object GetValue(AvaloniaProperty property)
{
throw new NotImplementedException();
}
public T GetValue<T>(AvaloniaProperty<T> property)
{
throw new NotImplementedException();
}
public void SetValue(AvaloniaProperty property, object value, BindingPriority priority)
{
throw new NotImplementedException();
}
public void SetValue<T>(AvaloniaProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public IDisposable Bind(AvaloniaProperty property, IObservable<object> source, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public bool IsAnimating(AvaloniaProperty property)
{
throw new NotImplementedException();
}
public bool IsSet(AvaloniaProperty property)
{
throw new NotImplementedException();
}
public IDisposable Bind<T>(AvaloniaProperty<T> property, IObservable<T> source, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public void NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
throw new NotImplementedException();
}
public void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
throw new NotImplementedException();
}
public void NotifyResourcesChanged(ResourcesChangedEventArgs e)
{
throw new NotImplementedException();
get => Parent;
set => ((ISetLogicalParent)this).SetParent(value);
}
}

3
tests/Avalonia.Styling.UnitTests/SelectorTests_Name.cs

@ -1,6 +1,7 @@
// 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.Controls;
using Moq;
using Xunit;
@ -52,7 +53,7 @@ namespace Avalonia.Styling.UnitTests
Assert.Equal("Control1#foo", target.ToString());
}
public class Control1 : TestControlBase
public class Control1 : Control
{
}
}

4
tests/Avalonia.Styling.UnitTests/SelectorTests_Not.cs

@ -103,11 +103,11 @@ namespace Avalonia.Styling.UnitTests
Assert.Equal(typeof(Control1), target.TargetType);
}
public class Control1 : TestControlBase
public class Control1 : Control
{
}
public class Control2 : TestControlBase
public class Control2 : Control
{
}
}

5
tests/Avalonia.Styling.UnitTests/SelectorTests_OfType.cs

@ -1,6 +1,7 @@
// 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.Controls;
using Moq;
using Xunit;
@ -44,11 +45,11 @@ namespace Avalonia.Styling.UnitTests
Assert.Equal(SelectorMatchResult.AlwaysThisType, target.Match(control).Result);
}
public class Control1 : TestControlBase
public class Control1 : Control
{
}
public class Control2 : TestControlBase
public class Control2 : Control
{
}
}

9
tests/Avalonia.Styling.UnitTests/SelectorTests_Or.cs

@ -1,6 +1,7 @@
// 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.Controls;
using Xunit;
namespace Avalonia.Styling.UnitTests
@ -78,7 +79,7 @@ namespace Avalonia.Styling.UnitTests
default(Selector).OfType<Control1>().Class("foo"),
default(Selector).OfType<Control2>().Class("bar"));
Assert.Equal(typeof(TestControlBase), target.TargetType);
Assert.Equal(typeof(Control), target.TargetType);
}
[Fact]
@ -91,15 +92,15 @@ namespace Avalonia.Styling.UnitTests
Assert.Equal(null, target.TargetType);
}
public class Control1 : TestControlBase
public class Control1 : Control
{
}
public class Control2 : TestControlBase
public class Control2 : Control
{
}
public class Control3 : TestControlBase
public class Control3 : Control
{
}
}

83
tests/Avalonia.Styling.UnitTests/TestControlBase.cs

@ -1,83 +0,0 @@
// 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 System.Reactive;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Data;
namespace Avalonia.Styling.UnitTests
{
public class TestControlBase : IStyleable
{
public TestControlBase()
{
Classes = new Classes();
SubscribeCheckObservable = new TestObservable();
}
#pragma warning disable CS0067 // Event not used
public event EventHandler<AvaloniaPropertyChangedEventArgs> PropertyChanged;
public event EventHandler<AvaloniaPropertyChangedEventArgs> InheritablePropertyChanged;
#pragma warning restore CS0067
public string Name { get; set; }
public virtual Classes Classes { get; set; }
public Type StyleKey => GetType();
public TestObservable SubscribeCheckObservable { get; private set; }
public ITemplatedControl TemplatedParent
{
get;
set;
}
IAvaloniaReadOnlyList<string> IStyleable.Classes => Classes;
IObservable<IStyleable> IStyleable.StyleDetach { get; }
public object GetValue(AvaloniaProperty property)
{
throw new NotImplementedException();
}
public T GetValue<T>(AvaloniaProperty<T> property)
{
throw new NotImplementedException();
}
public void SetValue(AvaloniaProperty property, object value, BindingPriority priority)
{
throw new NotImplementedException();
}
public void SetValue<T>(AvaloniaProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public bool IsAnimating(AvaloniaProperty property)
{
throw new NotImplementedException();
}
public bool IsSet(AvaloniaProperty property)
{
throw new NotImplementedException();
}
public IDisposable Bind(AvaloniaProperty property, IObservable<object> source, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public IDisposable Bind<T>(AvaloniaProperty<T> property, IObservable<T> source, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
}
}

81
tests/Avalonia.Styling.UnitTests/TestTemplatedControl.cs

@ -1,81 +0,0 @@
// 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 System.Reactive;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Data;
namespace Avalonia.Styling.UnitTests
{
public abstract class TestTemplatedControl : ITemplatedControl, IStyleable
{
public event EventHandler<AvaloniaPropertyChangedEventArgs> PropertyChanged;
public event EventHandler<AvaloniaPropertyChangedEventArgs> InheritablePropertyChanged;
public abstract Classes Classes
{
get;
}
public abstract string Name
{
get;
}
public abstract Type StyleKey
{
get;
}
public abstract ITemplatedControl TemplatedParent
{
get;
}
IAvaloniaReadOnlyList<string> IStyleable.Classes => Classes;
IObservable<IStyleable> IStyleable.StyleDetach { get; }
public object GetValue(AvaloniaProperty property)
{
throw new NotImplementedException();
}
public T GetValue<T>(AvaloniaProperty<T> property)
{
throw new NotImplementedException();
}
public void SetValue(AvaloniaProperty property, object value, BindingPriority priority)
{
throw new NotImplementedException();
}
public void SetValue<T>(AvaloniaProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public IDisposable Bind(AvaloniaProperty property, IObservable<object> source, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public IDisposable Bind<T>(AvaloniaProperty<T> property, IObservable<T> source, BindingPriority priority = BindingPriority.LocalValue)
{
throw new NotImplementedException();
}
public bool IsAnimating(AvaloniaProperty property)
{
throw new NotImplementedException();
}
public bool IsSet(AvaloniaProperty property)
{
throw new NotImplementedException();
}
}
}
Loading…
Cancel
Save