diff --git a/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs b/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs index 7973ad72e5..ad2cec2ae3 100644 --- a/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs +++ b/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs @@ -56,7 +56,7 @@ namespace Avalonia.AndroidTestApplication { Margin = new Thickness(30), Background = Brushes.Yellow, - Children = new Avalonia.Controls.Controls + Children = { new TextBlock { diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 8b93cf2fb1..34278c397f 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -51,6 +51,21 @@ namespace Avalonia /// private EventHandler _propertyChanged; + private DeferredSetter _directDeferredSetter; + + /// + /// Delayed setter helper for direct properties. Used to fix #855. + /// + private DeferredSetter DirectPropertyDeferredSetter + { + get + { + return _directDeferredSetter ?? + (_directDeferredSetter = new DeferredSetter()); + } + } + + /// /// Initializes a new instance of the class. /// @@ -552,6 +567,45 @@ namespace Avalonia } } + /// + /// A callback type for encapsulating complex logic for setting direct properties. + /// + /// The type of the property. + /// The value to which to set the property. + /// The backing field for the property. + /// A wrapper for the property-changed notification. + protected delegate void SetAndRaiseCallback(T value, ref T field, Action notifyWrapper); + + /// + /// Sets the backing field for a direct avalonia property, raising the + /// event if the value has changed. + /// + /// The type of the property. + /// The property. + /// The backing field. + /// A callback called to actually set the value to the backing field. + /// The value. + /// + /// True if the value changed, otherwise false. + /// + protected bool SetAndRaise( + AvaloniaProperty property, + ref T field, + SetAndRaiseCallback setterCallback, + T value) + { + Contract.Requires(setterCallback != null); + return DirectPropertyDeferredSetter.SetAndNotify( + property, + ref field, + (object val, ref T backing, Action notify) => + { + setterCallback((T)val, ref backing, notify); + return true; + }, + value); + } + /// /// Sets the backing field for a direct avalonia property, raising the /// event if the value has changed. @@ -566,17 +620,32 @@ namespace Avalonia protected bool SetAndRaise(AvaloniaProperty property, ref T field, T value) { VerifyAccess(); - if (!object.Equals(field, value)) - { - var old = field; - field = value; - RaisePropertyChanged(property, old, value, BindingPriority.LocalValue); - return true; - } - else - { - return false; - } + return SetAndRaise( + property, + ref field, + (T val, ref T backing, Action notifyWrapper) + => SetAndRaiseCore(property, ref backing, val, notifyWrapper), + value); + } + + /// + /// Default assignment logic for SetAndRaise. + /// + /// The type of the property. + /// The property. + /// The backing field. + /// The value. + /// A wrapper for the property-changed notification. + /// + /// True if the value changed, otherwise false. + /// + private bool SetAndRaiseCore(AvaloniaProperty property, ref T field, T value, Action notifyWrapper) + { + var old = field; + field = value; + + notifyWrapper(() => RaisePropertyChanged(property, old, value, BindingPriority.LocalValue)); + return true; } /// diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index e96679e643..1da2ecb942 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -138,17 +138,9 @@ namespace Avalonia AvaloniaProperty property, BindingPriority priority = BindingPriority.LocalValue) { - // TODO: Subject.Create is not yet in stable Rx : once it is, remove the - // AnonymousSubject classes and use Subject.Create. - var output = new Subject(); - var result = new AnonymousSubject( - Observer.Create( - x => output.OnNext(x), - e => output.OnError(e), - () => output.OnCompleted()), + return Subject.Create( + Observer.Create(x => o.SetValue(property, x, priority)), o.GetObservable(property)); - o.Bind(property, output, priority); - return result; } /// @@ -169,17 +161,9 @@ namespace Avalonia AvaloniaProperty property, BindingPriority priority = BindingPriority.LocalValue) { - // TODO: Subject.Create is not yet in stable Rx : once it is, remove the - // AnonymousSubject classes from this file and use Subject.Create. - var output = new Subject(); - var result = new AnonymousSubject( - Observer.Create( - x => output.OnNext(x), - e => output.OnError(e), - () => output.OnCompleted()), + return Subject.Create( + Observer.Create(x => o.SetValue(property, x, priority)), o.GetObservable(property)); - o.Bind(property, output, priority); - return result; } /// diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs index 12a9e20528..9b5318083a 100644 --- a/src/Avalonia.Base/PriorityValue.cs +++ b/src/Avalonia.Base/PriorityValue.cs @@ -28,8 +28,10 @@ namespace Avalonia { private readonly Type _valueType; private readonly SingleOrDictionary _levels = new SingleOrDictionary(); - private object _value; + private readonly Func _validate; + private static readonly DeferredSetter delayedSetter = new DeferredSetter(); + private (object value, int priority) _value; /// /// Initializes a new instance of the class. @@ -47,8 +49,7 @@ namespace Avalonia Owner = owner; Property = property; _valueType = valueType; - _value = AvaloniaProperty.UnsetValue; - ValuePriority = int.MaxValue; + _value = (AvaloniaProperty.UnsetValue, int.MaxValue); _validate = validate; } @@ -77,16 +78,12 @@ namespace Avalonia /// /// Gets the current value. /// - public object Value => _value; + public object Value => _value.value; /// /// Gets the priority of the binding that is currently active. /// - public int ValuePriority - { - get; - private set; - } + public int ValuePriority => _value.priority; /// /// Adds a new binding. @@ -246,25 +243,36 @@ namespace Avalonia /// The priority level that the value came from. private void UpdateValue(object value, int priority) { - var notification = value as BindingNotification; + delayedSetter.SetAndNotify(this, + ref _value, + UpdateCore, + (value, priority)); + } + + private bool UpdateCore( + (object value, int priority) update, + ref (object value, int priority) backing, + Action notify) + { + var val = update.value; + var notification = val as BindingNotification; object castValue; if (notification != null) { - value = (notification.HasValue) ? notification.Value : null; + val = (notification.HasValue) ? notification.Value : null; } - if (TypeUtilities.TryConvertImplicit(_valueType, value, out castValue)) + if (TypeUtilities.TryConvertImplicit(_valueType, val, out castValue)) { - var old = _value; + var old = backing.value; if (_validate != null && castValue != AvaloniaProperty.UnsetValue) { castValue = _validate(castValue); } - ValuePriority = priority; - _value = castValue; + backing = (castValue, update.priority); if (notification?.HasValue == true) { @@ -273,7 +281,7 @@ namespace Avalonia if (notification == null || notification.HasValue) { - Owner?.Changed(this, old, _value); + notify(() => Owner?.Changed(this, old, Value)); } if (notification != null) @@ -284,14 +292,15 @@ namespace Avalonia else { Logger.Error( - LogArea.Binding, + LogArea.Binding, Owner, "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})", - Property.Name, - _valueType, - value, - value?.GetType()); + Property.Name, + _valueType, + val, + val?.GetType()); } + return true; } } } diff --git a/src/Avalonia.Base/Reactive/AnonymousSubject`1.cs b/src/Avalonia.Base/Reactive/AnonymousSubject`1.cs deleted file mode 100644 index c7bed15ecb..0000000000 --- a/src/Avalonia.Base/Reactive/AnonymousSubject`1.cs +++ /dev/null @@ -1,16 +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.Subjects; - -namespace Avalonia.Reactive -{ - public class AnonymousSubject : AnonymousSubject, ISubject - { - public AnonymousSubject(IObserver observer, IObservable observable) - : base(observer, observable) - { - } - } -} diff --git a/src/Avalonia.Base/Reactive/AnonymousSubject`2.cs b/src/Avalonia.Base/Reactive/AnonymousSubject`2.cs deleted file mode 100644 index 18551d564e..0000000000 --- a/src/Avalonia.Base/Reactive/AnonymousSubject`2.cs +++ /dev/null @@ -1,49 +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.Subjects; - -namespace Avalonia.Reactive -{ - public class AnonymousSubject : ISubject - { - private readonly IObserver _observer; - private readonly IObservable _observable; - - public AnonymousSubject(IObserver observer, IObservable observable) - { - _observer = observer; - _observable = observable; - } - - public void OnCompleted() - { - _observer.OnCompleted(); - } - - public void OnError(Exception error) - { - if (error == null) - throw new ArgumentNullException("error"); - - _observer.OnError(error); - } - - public void OnNext(T value) - { - _observer.OnNext(value); - } - - public IDisposable Subscribe(IObserver observer) - { - if (observer == null) - throw new ArgumentNullException("observer"); - - // - // [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence. - // - return _observable.Subscribe/*Unsafe*/(observer); - } - } -} diff --git a/src/Avalonia.Base/Utilities/DeferredSetter.cs b/src/Avalonia.Base/Utilities/DeferredSetter.cs new file mode 100644 index 0000000000..fdfa160134 --- /dev/null +++ b/src/Avalonia.Base/Utilities/DeferredSetter.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Avalonia.Utilities +{ + /// + /// A utility class to enable deferring assignment until after property-changed notifications are sent. + /// + /// The type of the object that represents the property. + /// The type of value with which to track the delayed assignment. + class DeferredSetter + where TProperty: class + { + private struct NotifyDisposable : IDisposable + { + private readonly SettingStatus status; + + internal NotifyDisposable(SettingStatus status) + { + this.status = status; + status.Notifying = true; + } + + public void Dispose() + { + status.Notifying = false; + } + } + + /// + /// Information on current setting/notification status of a property. + /// + private class SettingStatus + { + public bool Notifying { get; set; } + + private Queue pendingValues; + + public Queue PendingValues + { + get + { + return pendingValues ?? (pendingValues = new Queue()); + } + } + } + + private readonly ConditionalWeakTable setRecords = new ConditionalWeakTable(); + + /// + /// Mark the property as currently notifying. + /// + /// The property to mark as notifying. + /// Returns a disposable that when disposed, marks the property as done notifying. + private NotifyDisposable MarkNotifying(TProperty property) + { + Contract.Requires(!IsNotifying(property)); + + return new NotifyDisposable(setRecords.GetOrCreateValue(property)); + } + + /// + /// Check if the property is currently notifying listeners. + /// + /// The property. + /// If the property is currently notifying listeners. + private bool IsNotifying(TProperty property) + => setRecords.TryGetValue(property, out var value) && value.Notifying; + + /// + /// Add a pending assignment for the property. + /// + /// The property. + /// The value to assign. + private void AddPendingSet(TProperty property, TSetRecord value) + { + Contract.Requires(IsNotifying(property)); + + setRecords.GetOrCreateValue(property).PendingValues.Enqueue(value); + } + + /// + /// Checks if there are any pending assignments for the property. + /// + /// The property to check. + /// If the property has any pending assignments. + private bool HasPendingSet(TProperty property) + { + return setRecords.TryGetValue(property, out var status) && status.PendingValues.Count != 0; + } + + /// + /// Gets the first pending assignment for the property. + /// + /// The property to check. + /// The first pending assignment for the property. + private TSetRecord GetFirstPendingSet(TProperty property) + { + return setRecords.GetOrCreateValue(property).PendingValues.Dequeue(); + } + + public delegate bool SetterDelegate(TSetRecord record, ref TValue backing, Action notifyCallback); + + /// + /// Set the property and notify listeners while ensuring we don't get into a stack overflow as happens with #855 and #824 + /// + /// The property to set. + /// The backing field for the property + /// + /// A callback that actually sets the property. + /// The first parameter is the value to set, and the second is a wrapper that takes a callback that sends the property-changed notification. + /// + /// The value to try to set. + public bool SetAndNotify( + TProperty property, + ref TValue backing, + SetterDelegate setterCallback, + TSetRecord value) + { + Contract.Requires(setterCallback != null); + if (!IsNotifying(property)) + { + bool updated = false; + if (!object.Equals(value, backing)) + { + updated = setterCallback(value, ref backing, notification => + { + using (MarkNotifying(property)) + { + notification(); + } + }); + } + while (HasPendingSet(property)) + { + updated |= setterCallback(GetFirstPendingSet(property), ref backing, notification => + { + using (MarkNotifying(property)) + { + notification(); + } + }); + } + return updated; + } + else if(!object.Equals(value, backing)) + { + AddPendingSet(property, value); + } + return false; + } + } +} diff --git a/src/Avalonia.Controls/IPanel.cs b/src/Avalonia.Controls/IPanel.cs index 5bcfd33e86..ce2c63ffc8 100644 --- a/src/Avalonia.Controls/IPanel.cs +++ b/src/Avalonia.Controls/IPanel.cs @@ -9,8 +9,8 @@ namespace Avalonia.Controls public interface IPanel : IControl { /// - /// Gets or sets the children of the . + /// Gets the children of the . /// - Controls Children { get; set; } + Controls Children { get; } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index 6448f11491..a2cb013300 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -25,8 +25,6 @@ namespace Avalonia.Controls public static readonly StyledProperty BackgroundProperty = Border.BackgroundProperty.AddOwner(); - private readonly Controls _children = new Controls(); - /// /// Initializes static members of the class. /// @@ -40,38 +38,14 @@ namespace Avalonia.Controls /// public Panel() { - _children.CollectionChanged += ChildrenChanged; + Children.CollectionChanged += ChildrenChanged; } /// - /// Gets or sets the children of the . + /// Gets the children of the . /// - /// - /// Even though this property can be set, the setter is only intended for use in object - /// initializers. Assigning to this property does not change the underlying collection, - /// it simply clears the existing collection and adds the contents of the assigned - /// collection. - /// [Content] - public Controls Children - { - get - { - return _children; - } - - set - { - Contract.Requires(value != null); - - if (_children != value) - { - VisualChildren.Clear(); - _children.Clear(); - _children.AddRange(value); - } - } - } + public Controls Children { get; } = new Controls(); /// /// Gets or Sets Panel background brush. diff --git a/src/Avalonia.Controls/Primitives/RangeBase.cs b/src/Avalonia.Controls/Primitives/RangeBase.cs index b69dbba637..b46562e99e 100644 --- a/src/Avalonia.Controls/Primitives/RangeBase.cs +++ b/src/Avalonia.Controls/Primitives/RangeBase.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 Avalonia.Data; using Avalonia.Utilities; namespace Avalonia.Controls.Primitives @@ -36,7 +37,8 @@ namespace Avalonia.Controls.Primitives AvaloniaProperty.RegisterDirect( nameof(Value), o => o.Value, - (o, v) => o.Value = v); + (o, v) => o.Value = v, + defaultBindingMode: BindingMode.TwoWay); /// /// Defines the property. diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 563d394919..2e668fda95 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -151,15 +151,23 @@ namespace Avalonia.Controls.Primitives { if (_updateCount == 0) { - var old = SelectedIndex; - var effective = (value >= 0 && value < Items?.Cast().Count()) ? value : -1; - - if (old != effective) + SetAndRaise(SelectedIndexProperty, ref _selectedIndex, (int val, ref int backing, Action notifyWrapper) => { - _selectedIndex = effective; - RaisePropertyChanged(SelectedIndexProperty, old, effective, BindingPriority.LocalValue); - SelectedItem = ElementAt(Items, effective); - } + var old = backing; + var effective = (val >= 0 && val < Items?.Cast().Count()) ? val : -1; + + if (old != effective) + { + backing = effective; + notifyWrapper(() => + RaisePropertyChanged( + SelectedIndexProperty, + old, + effective, + BindingPriority.LocalValue)); + SelectedItem = ElementAt(Items, effective); + } + }, value); } else { @@ -183,31 +191,41 @@ namespace Avalonia.Controls.Primitives { if (_updateCount == 0) { - var old = SelectedItem; - var index = IndexOf(Items, value); - var effective = index != -1 ? value : null; - - if (!object.Equals(effective, old)) + SetAndRaise(SelectedItemProperty, ref _selectedItem, (object val, ref object backing, Action notifyWrapper) => { - _selectedItem = effective; - RaisePropertyChanged(SelectedItemProperty, old, effective, BindingPriority.LocalValue); - SelectedIndex = index; + var old = backing; + var index = IndexOf(Items, val); + var effective = index != -1 ? val : null; - if (effective != null) + if (!object.Equals(effective, old)) { - if (SelectedItems.Count != 1 || SelectedItems[0] != effective) + backing = effective; + + notifyWrapper(() => + RaisePropertyChanged( + SelectedItemProperty, + old, + effective, + BindingPriority.LocalValue)); + + SelectedIndex = index; + + if (effective != null) + { + if (SelectedItems.Count != 1 || SelectedItems[0] != effective) + { + _syncingSelectedItems = true; + SelectedItems.Clear(); + SelectedItems.Add(effective); + _syncingSelectedItems = false; + } + } + else if (SelectedItems.Count > 0) { - _syncingSelectedItems = true; SelectedItems.Clear(); - SelectedItems.Add(effective); - _syncingSelectedItems = false; } } - else if (SelectedItems.Count > 0) - { - SelectedItems.Clear(); - } - } + }, value); } else { diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index cd77d9cc88..c75150ca6d 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -2,22 +2,22 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections; -using System.Collections.Generic; +using System.ComponentModel; +using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reactive.Subjects; -using Microsoft.Reactive.Testing; +using System.Threading; +using System.Threading.Tasks; using Avalonia.Data; using Avalonia.Logging; -using Avalonia.UnitTests; -using Xunit; -using System.Threading.Tasks; +using Avalonia.Markup.Xaml.Data; using Avalonia.Platform; -using System.Threading; -using Moq; -using System.Reactive.Disposables; -using System.Reactive.Concurrency; using Avalonia.Threading; +using Avalonia.UnitTests; +using Avalonia.Diagnostics; +using Microsoft.Reactive.Testing; +using Moq; +using Xunit; namespace Avalonia.Base.UnitTests { @@ -363,7 +363,7 @@ namespace Avalonia.Base.UnitTests Assert.True(called); } } - + [Fact] public async Task Bind_With_Scheduler_Executes_On_Scheduler() { @@ -387,6 +387,37 @@ namespace Avalonia.Base.UnitTests } } + [Fact] + public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values() + { + var viewModel = new TestStackOverflowViewModel() + { + Value = 50 + }; + + var target = new Class1(); + + target.Bind(Class1.DoubleValueProperty, + new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel }); + + var child = new Class1(); + + child[!!Class1.DoubleValueProperty] = target[!!Class1.DoubleValueProperty]; + + Assert.Equal(1, viewModel.SetterInvokedCount); + + // Issues #855 and #824 were causing a StackOverflowException at this point. + target.DoubleValue = 51.001; + + Assert.Equal(2, viewModel.SetterInvokedCount); + + double expected = 51; + + Assert.Equal(expected, viewModel.Value); + Assert.Equal(expected, target.DoubleValue); + Assert.Equal(expected, child.DoubleValue); + } + [Fact] public void IsAnimating_On_Property_With_No_Value_Returns_False() { @@ -445,6 +476,15 @@ namespace Avalonia.Base.UnitTests public static readonly StyledProperty QuxProperty = AvaloniaProperty.Register("Qux", 5.6); + + public static readonly StyledProperty DoubleValueProperty = + AvaloniaProperty.Register(nameof(DoubleValue)); + + public double DoubleValue + { + get { return GetValue(DoubleValueProperty); } + set { SetValue(DoubleValueProperty, value); } + } } private class Class2 : Class1 @@ -471,5 +511,40 @@ namespace Avalonia.Base.UnitTests return InstancedBinding.OneTime(_source); } } + + private class TestStackOverflowViewModel : INotifyPropertyChanged + { + public int SetterInvokedCount { get; private set; } + + public const int MaxInvokedCount = 1000; + + private double _value; + + public event PropertyChangedEventHandler PropertyChanged; + + public double Value + { + get { return _value; } + set + { + if (_value != value) + { + SetterInvokedCount++; + if (SetterInvokedCount < MaxInvokedCount) + { + _value = (int)value; + if (_value > 75) _value = 75; + if (_value < 25) _value = 25; + } + else + { + _value = value; + } + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value))); + } + } + } + } } -} +} \ No newline at end of file diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index e9cb2bf450..6d10f50276 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -3,10 +3,11 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Reactive.Subjects; -using Avalonia; using Avalonia.Data; using Avalonia.Logging; +using Avalonia.Markup.Xaml.Data; using Avalonia.UnitTests; using Xunit; @@ -208,7 +209,7 @@ namespace Avalonia.Base.UnitTests { var target = new Class1(); - Assert.Throws(() => + Assert.Throws(() => target.SetValue(Class1.BarProperty, "newvalue")); } @@ -217,7 +218,7 @@ namespace Avalonia.Base.UnitTests { var target = new Class1(); - Assert.Throws(() => + Assert.Throws(() => target.SetValue((AvaloniaProperty)Class1.BarProperty, "newvalue")); } @@ -227,7 +228,7 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); var source = new Subject(); - Assert.Throws(() => + Assert.Throws(() => target.Bind(Class1.BarProperty, source)); } @@ -439,12 +440,46 @@ namespace Avalonia.Base.UnitTests Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata().DefaultBindingMode); } + [Fact] + public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values() + { + var viewModel = new TestStackOverflowViewModel() + { + Value = 50 + }; + + var target = new Class1(); + + target.Bind(Class1.DoubleValueProperty, new Binding("Value") + { + Mode = BindingMode.TwoWay, + Source = viewModel + }); + + var child = new Class1(); + + child[!!Class1.DoubleValueProperty] = target[!!Class1.DoubleValueProperty]; + + Assert.Equal(1, viewModel.SetterInvokedCount); + + // Issues #855 and #824 were causing a StackOverflowException at this point. + target.DoubleValue = 51.001; + + Assert.Equal(2, viewModel.SetterInvokedCount); + + double expected = 51; + + Assert.Equal(expected, viewModel.Value); + Assert.Equal(expected, target.DoubleValue); + Assert.Equal(expected, child.DoubleValue); + } + private class Class1 : AvaloniaObject { public static readonly DirectProperty FooProperty = AvaloniaProperty.RegisterDirect( - "Foo", - o => o.Foo, + "Foo", + o => o.Foo, (o, v) => o.Foo = v, unsetValue: "unset"); @@ -453,14 +488,21 @@ namespace Avalonia.Base.UnitTests public static readonly DirectProperty BazProperty = AvaloniaProperty.RegisterDirect( - "Bar", - o => o.Baz, - (o,v) => o.Baz = v, + "Bar", + o => o.Baz, + (o, v) => o.Baz = v, unsetValue: -1); + public static readonly DirectProperty DoubleValueProperty = + AvaloniaProperty.RegisterDirect( + nameof(DoubleValue), + o => o.DoubleValue, + (o, v) => o.DoubleValue = v); + private string _foo = "initial"; private readonly string _bar = "bar"; private int _baz = 5; + private double _doubleValue; public string Foo { @@ -478,6 +520,12 @@ namespace Avalonia.Base.UnitTests get { return _baz; } set { SetAndRaise(BazProperty, ref _baz, value); } } + + public double DoubleValue + { + get { return _doubleValue; } + set { SetAndRaise(DoubleValueProperty, ref _doubleValue, value); } + } } private class Class2 : AvaloniaObject @@ -497,5 +545,40 @@ namespace Avalonia.Base.UnitTests set { SetAndRaise(FooProperty, ref _foo, value); } } } + + private class TestStackOverflowViewModel : INotifyPropertyChanged + { + public int SetterInvokedCount { get; private set; } + + public const int MaxInvokedCount = 1000; + + private double _value; + + public event PropertyChangedEventHandler PropertyChanged; + + public double Value + { + get { return _value; } + set + { + if (_value != value) + { + SetterInvokedCount++; + if (SetterInvokedCount < MaxInvokedCount) + { + _value = (int)value; + if (_value > 75) _value = 75; + if (_value < 25) _value = 25; + } + else + { + _value = value; + } + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value))); + } + } + } + } } -} +} \ No newline at end of file diff --git a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj index 1d987e2238..c16b89e0b6 100644 --- a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj +++ b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj @@ -49,6 +49,7 @@ + diff --git a/tests/Avalonia.Benchmarks/Base/Properties.cs b/tests/Avalonia.Benchmarks/Base/Properties.cs new file mode 100644 index 0000000000..0a020961d5 --- /dev/null +++ b/tests/Avalonia.Benchmarks/Base/Properties.cs @@ -0,0 +1,43 @@ +using System; +using System.Reactive.Subjects; +using BenchmarkDotNet.Attributes; + +namespace Avalonia.Benchmarks.Base +{ + [MemoryDiagnoser] + public class AvaloniaObjectBenchmark + { + private Class1 target = new Class1(); + private Subject intBinding = new Subject(); + + public AvaloniaObjectBenchmark() + { + target.SetValue(Class1.IntProperty, 123); + } + + [Benchmark] + public void ClearAndSetIntProperty() + { + target.ClearValue(Class1.IntProperty); + target.SetValue(Class1.IntProperty, 123); + } + + [Benchmark] + public void BindIntProperty() + { + using (target.Bind(Class1.IntProperty, intBinding)) + { + for (var i = 0; i < 100; ++i) + { + intBinding.OnNext(i); + } + } + } + + class Class1 : AvaloniaObject + { + public static readonly AvaloniaProperty IntProperty = + AvaloniaProperty.Register("Int"); + } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/DockPanelTests.cs b/tests/Avalonia.Controls.UnitTests/DockPanelTests.cs index db4c3c0eca..3de67839a7 100644 --- a/tests/Avalonia.Controls.UnitTests/DockPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DockPanelTests.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls.UnitTests { var target = new DockPanel { - Children = new Controls + Children = { new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Top }, new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Bottom }, @@ -38,7 +38,7 @@ namespace Avalonia.Controls.UnitTests { var target = new DockPanel { - Children = new Controls + Children = { new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Left }, new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Right }, diff --git a/tests/Avalonia.Controls.UnitTests/DropDownTests.cs b/tests/Avalonia.Controls.UnitTests/DropDownTests.cs index b5de8c67fa..29e60a8f83 100644 --- a/tests/Avalonia.Controls.UnitTests/DropDownTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DropDownTests.cs @@ -89,7 +89,7 @@ namespace Avalonia.Controls.UnitTests return new Panel { Name = "container", - Children = new Controls + Children = { new ContentControl { diff --git a/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs b/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs index 5d622a5fc1..a1ba608ec6 100644 --- a/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs @@ -22,14 +22,14 @@ namespace Avalonia.Controls.UnitTests { var grid = new Grid() { - RowDefinitions = new RowDefinitions("*,Auto,*"), - ColumnDefinitions = new ColumnDefinitions("*,*"), - Children = new Controls() - { - new Border { [Grid.RowProperty] = 0 }, - new GridSplitter { [Grid.RowProperty] = 1, Name = "splitter" }, - new Border { [Grid.RowProperty] = 2 } - } + RowDefinitions = new RowDefinitions("*,Auto,*"), + ColumnDefinitions = new ColumnDefinitions("*,*"), + Children = + { + new Border { [Grid.RowProperty] = 0 }, + new GridSplitter { [Grid.RowProperty] = 1, Name = "splitter" }, + new Border { [Grid.RowProperty] = 2 } + } }; var root = new TestRoot { Child = grid }; @@ -43,14 +43,14 @@ namespace Avalonia.Controls.UnitTests { var grid = new Grid() { - ColumnDefinitions = new ColumnDefinitions("*,Auto,*"), - RowDefinitions = new RowDefinitions("*,*"), - Children = new Controls() - { - new Border { [Grid.ColumnProperty] = 0 }, - new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" }, - new Border { [Grid.ColumnProperty] = 2 }, - } + ColumnDefinitions = new ColumnDefinitions("*,Auto,*"), + RowDefinitions = new RowDefinitions("*,*"), + Children = + { + new Border { [Grid.ColumnProperty] = 0 }, + new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" }, + new Border { [Grid.ColumnProperty] = 2 }, + } }; var root = new TestRoot { Child = grid }; @@ -64,14 +64,14 @@ namespace Avalonia.Controls.UnitTests { var grid = new Grid() { - ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"), - RowDefinitions = new RowDefinitions("Auto,Auto"), - Children = new Controls() - { - new Border { [Grid.ColumnProperty] = 0 }, - new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" }, - new Border { [Grid.ColumnProperty] = 2 }, - } + ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"), + RowDefinitions = new RowDefinitions("Auto,Auto"), + Children = + { + new Border { [Grid.ColumnProperty] = 0 }, + new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" }, + new Border { [Grid.ColumnProperty] = 2 }, + } }; var root = new TestRoot { Child = grid }; @@ -99,11 +99,11 @@ namespace Avalonia.Controls.UnitTests var grid = new Grid() { - RowDefinitions = rowDefinitions, - Children = new Controls() - { - control1, splitter, control2 - } + RowDefinitions = rowDefinitions, + Children = + { + control1, splitter, control2 + } }; var root = new TestRoot { Child = grid }; @@ -131,14 +131,14 @@ namespace Avalonia.Controls.UnitTests { var grid = new Grid() { - ColumnDefinitions = new ColumnDefinitions("Auto,*,*"), - RowDefinitions = new RowDefinitions("*,*"), - Children = new Controls() - { - new GridSplitter { [Grid.ColumnProperty] = 0, Name = "splitter" }, - new Border { [Grid.ColumnProperty] = 1 }, - new Border { [Grid.ColumnProperty] = 2 }, - } + ColumnDefinitions = new ColumnDefinitions("Auto,*,*"), + RowDefinitions = new RowDefinitions("*,*"), + Children = + { + new GridSplitter { [Grid.ColumnProperty] = 0, Name = "splitter" }, + new Border { [Grid.ColumnProperty] = 1 }, + new Border { [Grid.ColumnProperty] = 2 }, + } }; var root = new TestRoot { Child = grid }; @@ -171,11 +171,11 @@ namespace Avalonia.Controls.UnitTests var grid = new Grid() { - ColumnDefinitions = columnDefinitions, - Children = new Controls() - { - control1, splitter, control2 - } + ColumnDefinitions = columnDefinitions, + Children = + { + control1, splitter, control2 + } }; var root = new TestRoot { Child = grid }; diff --git a/tests/Avalonia.Controls.UnitTests/GridTests.cs b/tests/Avalonia.Controls.UnitTests/GridTests.cs index a10bb38322..c5aea6501f 100644 --- a/tests/Avalonia.Controls.UnitTests/GridTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridTests.cs @@ -24,7 +24,7 @@ namespace Avalonia.Controls.UnitTests new RowDefinition(GridLength.Auto), new RowDefinition(GridLength.Auto), }, - Children = new Controls + Children = { new Border { diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index c7992fe80f..fee4994ee3 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -1,11 +1,15 @@ // 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.Collections.Generic; +using System.ComponentModel; using System.Linq; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; +using Avalonia.Data; using Avalonia.Input; using Avalonia.LogicalTree; +using Avalonia.Markup.Xaml.Data; using Avalonia.Styling; using Avalonia.VisualTree; using Xunit; @@ -199,6 +203,71 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(1, target.SelectedIndex); } + [Fact] + public void SelectedItem_Should_Not_Cause_StackOverflow() + { + var viewModel = new TestStackOverflowViewModel() + { + Items = new List { "foo", "bar", "baz" } + }; + + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + DataContext = viewModel, + Items = viewModel.Items + }; + + target.Bind(ListBox.SelectedItemProperty, + new Binding("SelectedItem") { Mode = BindingMode.TwoWay }); + + Assert.Equal(0, viewModel.SetterInvokedCount); + + // In Issue #855, a Stackoverflow occured here. + target.SelectedItem = viewModel.Items[2]; + + Assert.Equal(viewModel.Items[1], target.SelectedItem); + Assert.Equal(1, viewModel.SetterInvokedCount); + } + + private class TestStackOverflowViewModel : INotifyPropertyChanged + { + public List Items { get; set; } + + public int SetterInvokedCount { get; private set; } + + public const int MaxInvokedCount = 1000; + + private string _selectedItem; + + public event PropertyChangedEventHandler PropertyChanged; + + public string SelectedItem + { + get { return _selectedItem; } + set + { + if (_selectedItem != value) + { + SetterInvokedCount++; + + int index = Items.IndexOf(value); + + if (MaxInvokedCount > SetterInvokedCount && index > 0) + { + _selectedItem = Items[index - 1]; + } + else + { + _selectedItem = value; + } + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem))); + } + } + } + } + private Control CreateListBoxTemplate(ITemplatedControl parent) { return new ScrollViewer @@ -237,4 +306,4 @@ namespace Avalonia.Controls.UnitTests target.Presenter.ApplyTemplate(); } } -} +} \ No newline at end of file diff --git a/tests/Avalonia.Controls.UnitTests/PanelTests.cs b/tests/Avalonia.Controls.UnitTests/PanelTests.cs index 29acdaa8d4..ed239120d6 100644 --- a/tests/Avalonia.Controls.UnitTests/PanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/PanelTests.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Linq; -using Avalonia.Collections; using Avalonia.LogicalTree; using Avalonia.VisualTree; using Xunit; @@ -24,18 +23,6 @@ namespace Avalonia.Controls.UnitTests Assert.Same(child.GetVisualParent(), panel); } - [Fact] - public void Setting_Controls_Should_Set_Child_Controls_Parent() - { - var panel = new Panel(); - var child = new Control(); - - panel.Children = new Controls { child }; - - Assert.Equal(child.Parent, panel); - Assert.Equal(child.GetLogicalParent(), panel); - } - [Fact] public void Removing_Control_From_Panel_Should_Clear_Child_Controls_Parent() { @@ -69,25 +56,6 @@ namespace Avalonia.Controls.UnitTests Assert.Null(child2.GetVisualParent()); } - [Fact] - public void Resetting_Panel_Children_Should_Clear_Child_Controls_Parent() - { - var panel = new Panel(); - var child1 = new Control(); - var child2 = new Control(); - - panel.Children.Add(child1); - panel.Children.Add(child2); - panel.Children = new Controls(); - - Assert.Null(child1.Parent); - Assert.Null(child1.GetLogicalParent()); - Assert.Null(child1.GetVisualParent()); - Assert.Null(child2.Parent); - Assert.Null(child2.GetLogicalParent()); - Assert.Null(child2.GetVisualParent()); - } - [Fact] public void Replacing_Panel_Children_Should_Clear_And_Set_Control_Parent() { @@ -147,18 +115,5 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new[] { child2, child1 }, panel.GetLogicalChildren()); Assert.Equal(new[] { child2, child1 }, panel.GetVisualChildren()); } - - [Fact] - public void Setting_Children_Should_Make_Controls_Appear_In_Logical_And_Visual_Children() - { - var panel = new Panel(); - var child = new Control(); - - panel.Children = new Controls { child }; - - Assert.Equal(new[] { child }, panel.Children); - Assert.Equal(new[] { child }, panel.GetLogicalChildren()); - Assert.Equal(new[] { child }, panel.GetVisualChildren()); - } } } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs index d3ed077cbf..2dfb30a9f0 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs @@ -2,7 +2,12 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.ComponentModel; using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Markup.Xaml.Data; +using Avalonia.Styling; using Xunit; namespace Avalonia.Controls.UnitTests.Primitives @@ -87,8 +92,111 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.Throws(() => target.Value = double.NegativeInfinity); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SetValue_Should_Not_Cause_StackOverflow(bool useXamlBinding) + { + var viewModel = new TestStackOverflowViewModel() + { + Value = 50 + }; + + Track track = null; + + var target = new TestRange() + { + Template = new FuncControlTemplate(c => + { + track = new Track() + { + Width = 100, + Orientation = Orientation.Horizontal, + [~~Track.MinimumProperty] = c[~~RangeBase.MinimumProperty], + [~~Track.MaximumProperty] = c[~~RangeBase.MaximumProperty], + + Name = "PART_Track", + Thumb = new Thumb() + }; + + if (useXamlBinding) + { + track.Bind(Track.ValueProperty, new Binding("Value") + { + Mode = BindingMode.TwoWay, + Source = c, + Priority = BindingPriority.Style + }); + } + else + { + track[~~Track.ValueProperty] = c[~~RangeBase.ValueProperty]; + } + + return track; + }), + Minimum = 0, + Maximum = 100, + DataContext = viewModel + }; + + target.Bind(TestRange.ValueProperty, new Binding("Value") { Mode = BindingMode.TwoWay }); + + target.ApplyTemplate(); + track.Measure(new Size(100, 0)); + track.Arrange(new Rect(0, 0, 100, 0)); + + Assert.Equal(1, viewModel.SetterInvokedCount); + + // Issues #855 and #824 were causing a StackOverflowException at this point. + target.Value = 51.001; + + Assert.Equal(2, viewModel.SetterInvokedCount); + + double expected = 51; + + Assert.Equal(expected, viewModel.Value); + Assert.Equal(expected, target.Value); + Assert.Equal(expected, track.Value); + } + private class TestRange : RangeBase { } + + private class TestStackOverflowViewModel : INotifyPropertyChanged + { + public int SetterInvokedCount { get; private set; } + + public const int MaxInvokedCount = 1000; + + private double _value; + + public event PropertyChangedEventHandler PropertyChanged; + + public double Value + { + get { return _value; } + set + { + if (_value != value) + { + SetterInvokedCount++; + if (SetterInvokedCount < MaxInvokedCount) + { + _value = (int)value; + if (_value > 75) _value = 75; + if (_value < 25) _value = 25; + } + else + { + _value = value; + } + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value))); + } + } + } + } } } \ No newline at end of file diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs index be3c34ac2e..cd71717619 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs @@ -67,7 +67,7 @@ namespace Avalonia.Controls.UnitTests.Primitives { Child = new Panel { - Children = new Controls + Children = { new TextBlock(), new Border(), @@ -101,7 +101,7 @@ namespace Avalonia.Controls.UnitTests.Primitives { Child = new Panel { - Children = new Controls + Children = { new TextBlock(), new Border(), @@ -124,7 +124,7 @@ namespace Avalonia.Controls.UnitTests.Primitives { Child = new Panel { - Children = new Controls + Children = { new TextBlock(), new Border(), @@ -189,7 +189,7 @@ namespace Avalonia.Controls.UnitTests.Primitives { return new StackPanel { - Children = new Controls + Children = { new TextBlock { diff --git a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs index 8f815a85c9..68dcfcb770 100644 --- a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs @@ -50,7 +50,7 @@ namespace Avalonia.Controls.UnitTests new RowDefinition(1, GridUnitType.Star), new RowDefinition(GridLength.Auto), }, - Children = new Controls + Children = { new ScrollContentPresenter { diff --git a/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs index 56412d732b..ba80cb779a 100644 --- a/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs @@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests { var target = new StackPanel { - Children = new Controls + Children = { new Border { Height = 20, Width = 120 }, new Border { Height = 30 }, @@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests var target = new StackPanel { Orientation = Orientation.Horizontal, - Children = new Controls + Children = { new Border { Width = 20, Height = 120 }, new Border { Width = 30 }, @@ -59,7 +59,7 @@ namespace Avalonia.Controls.UnitTests var target = new StackPanel { Gap = 10, - Children = new Controls + Children = { new Border { Height = 20, Width = 120 }, new Border { Height = 30 }, @@ -83,7 +83,7 @@ namespace Avalonia.Controls.UnitTests { Gap = 10, Orientation = Orientation.Horizontal, - Children = new Controls + Children = { new Border { Width = 20, Height = 120 }, new Border { Width = 30 }, @@ -106,7 +106,7 @@ namespace Avalonia.Controls.UnitTests var target = new StackPanel { Height = 60, - Children = new Controls + Children = { new Border { Height = 20, Width = 120 }, new Border { Height = 30 }, @@ -130,7 +130,7 @@ namespace Avalonia.Controls.UnitTests { Width = 60, Orientation = Orientation.Horizontal, - Children = new Controls + Children = { new Border { Width = 20, Height = 120 }, new Border { Width = 30 }, diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index 638d773cd0..67b224d6a0 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -272,7 +272,7 @@ namespace Avalonia.Controls.UnitTests { return new StackPanel { - Children = new Controls + Children = { new TabStrip { diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 17eccc96bc..625d9eb26e 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -432,7 +432,7 @@ namespace Avalonia.Controls.UnitTests { return new FuncControlTemplate(parent => new Panel { - Children = new Controls + Children = { new ContentPresenter { diff --git a/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs b/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs index 1a46e24b16..cd35627064 100644 --- a/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs @@ -12,12 +12,12 @@ namespace Avalonia.Controls.UnitTests { var target = new WrapPanel() { - Width = 100, - Children = new Controls - { - new Border { Height = 50, Width = 100 }, - new Border { Height = 50, Width = 100 }, - } + Width = 100, + Children = + { + new Border { Height = 50, Width = 100 }, + new Border { Height = 50, Width = 100 }, + } }; target.Measure(Size.Infinity); @@ -33,12 +33,12 @@ namespace Avalonia.Controls.UnitTests { var target = new WrapPanel() { - Width = 200, - Children = new Controls - { - new Border { Height = 50, Width = 100 }, - new Border { Height = 50, Width = 100 }, - } + Width = 200, + Children = + { + new Border { Height = 50, Width = 100 }, + new Border { Height = 50, Width = 100 }, + } }; target.Measure(Size.Infinity); @@ -54,13 +54,13 @@ namespace Avalonia.Controls.UnitTests { var target = new WrapPanel() { - Orientation = Orientation.Vertical, - Height = 120, - Children = new Controls - { - new Border { Height = 50, Width = 100 }, - new Border { Height = 50, Width = 100 }, - } + Orientation = Orientation.Vertical, + Height = 120, + Children = + { + new Border { Height = 50, Width = 100 }, + new Border { Height = 50, Width = 100 }, + } }; target.Measure(Size.Infinity); @@ -76,13 +76,13 @@ namespace Avalonia.Controls.UnitTests { var target = new WrapPanel() { - Orientation = Orientation.Vertical, - Height = 60, - Children = new Controls - { - new Border { Height = 50, Width = 100 }, - new Border { Height = 50, Width = 100 }, - } + Orientation = Orientation.Vertical, + Height = 60, + Children = + { + new Border { Height = 50, Width = 100 }, + new Border { Height = 50, Width = 100 }, + } }; target.Measure(Size.Infinity); diff --git a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Arrows.cs b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Arrows.cs index 2b9b0baf01..b81b724e2a 100644 --- a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Arrows.cs +++ b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Arrows.cs @@ -6,8 +6,6 @@ using Xunit; namespace Avalonia.Input.UnitTests { - using Controls = Controls.Controls; - public class KeyboardNavigationTests_Arrows { [Fact] @@ -18,12 +16,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { new Button { Name = "Button1" }, (current = new Button { Name = "Button2" }), @@ -33,7 +31,7 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -56,12 +54,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { new Button { Name = "Button1" }, new Button { Name = "Button2" }, @@ -71,7 +69,7 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { (next = new Button { Name = "Button4" }), new Button { Name = "Button5" }, @@ -94,12 +92,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { new Button { Name = "Button1" }, new Button { Name = "Button2" }, @@ -123,16 +121,16 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { new Button { Name = "Button1" }, new Button { Name = "Button2" }, @@ -144,7 +142,7 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { (next = new Button { Name = "Button4" }), new Button { Name = "Button5" }, @@ -167,7 +165,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { (next = new Button { Name = "Button1" }), } @@ -187,17 +185,17 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { (next = new Button { Name = "Button1" }), new Button { Name = "Button2" }, @@ -209,7 +207,7 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -232,12 +230,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, - Children = new Controls + Children = { new Button { Name = "Button1" }, (current = new Button { Name = "Button2" }), @@ -246,7 +244,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -269,12 +267,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, - Children = new Controls + Children = { (next = new Button { Name = "Button1" }), new Button { Name = "Button2" }, @@ -283,7 +281,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -306,12 +304,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, - Children = new Controls + Children = { new Button { Name = "Button1" }, (current = new Button { Name = "Button2" }), @@ -321,7 +319,7 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -343,12 +341,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, - Children = new Controls + Children = { new Button { Name = "Button1" }, new Button { Name = "Button2" }, @@ -358,7 +356,7 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -380,12 +378,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.None, - Children = new Controls + Children = { new Button { Name = "Button1" }, (current = new Button { Name = "Button2" }), @@ -395,7 +393,7 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -418,12 +416,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { new Button { Name = "Button1" }, (next = new Button { Name = "Button2" }), @@ -433,7 +431,7 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -456,12 +454,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { new Button { Name = "Button1" }, new Button { Name = "Button2" }, @@ -471,7 +469,7 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { (current = new Button { Name = "Button4" }), new Button { Name = "Button5" }, @@ -495,12 +493,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { new Button { Name = "Button1" }, new Button { Name = "Button2" }, @@ -524,16 +522,16 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { new Button { Name = "Button1" }, new Button { Name = "Button2" }, @@ -545,7 +543,7 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { (current = new Button { Name = "Button4" }), new Button { Name = "Button5" }, @@ -568,16 +566,16 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { (current = new Button { Name = "Button1" }), new Button { Name = "Button2" }, @@ -589,7 +587,7 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -632,12 +630,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, - Children = new Controls + Children = { (next = new Button { Name = "Button1" }), (current = new Button { Name = "Button2" }), @@ -647,7 +645,7 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -670,12 +668,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, - Children = new Controls + Children = { (current = new Button { Name = "Button1" }), new Button { Name = "Button2" }, @@ -685,7 +683,7 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -708,12 +706,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, - Children = new Controls + Children = { (next = new Button { Name = "Button1" }), (current = new Button { Name = "Button2" }), @@ -723,7 +721,7 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -745,12 +743,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, - Children = new Controls + Children = { (current = new Button { Name = "Button1" }), new Button { Name = "Button2" }, @@ -760,7 +758,7 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -783,7 +781,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, - Children = new Controls + Children = { (current = new Decorator { diff --git a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs index 2cc7a4281b..ad70dcd470 100644 --- a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs +++ b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs @@ -6,8 +6,6 @@ using Xunit; namespace Avalonia.Input.UnitTests { - using Controls = Controls.Controls; - public class KeyboardNavigationTests_Tab { [Fact] @@ -18,11 +16,11 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button1" }, (current = new Button { Name = "Button2" }), @@ -31,7 +29,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -54,11 +52,11 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button1" }, new Button { Name = "Button2" }, @@ -67,7 +65,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { (next = new Button { Name = "Button4" }), new Button { Name = "Button5" }, @@ -90,11 +88,11 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { (next = new Button { Name = "Button1" }), new Button { Name = "Button2" }, @@ -104,11 +102,11 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.None, - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -133,11 +131,11 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button1" }, new Button { Name = "Button2" }, @@ -161,15 +159,15 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button1" }, new Button { Name = "Button2" }, @@ -180,7 +178,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { (next = new Button { Name = "Button4" }), new Button { Name = "Button5" }, @@ -202,7 +200,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { (next = new Button { Name = "Button1" }), } @@ -221,15 +219,15 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { (next = new Button { Name = "Button1" }), new Button { Name = "Button2" }, @@ -240,7 +238,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -263,12 +261,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, - Children = new Controls + Children = { new Button { Name = "Button1" }, (current = new Button { Name = "Button2" }), @@ -277,7 +275,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -300,12 +298,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, - Children = new Controls + Children = { (next = new Button { Name = "Button1" }), new Button { Name = "Button2" }, @@ -314,7 +312,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -337,12 +335,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained, - Children = new Controls + Children = { new Button { Name = "Button1" }, (current = new Button { Name = "Button2" }), @@ -351,7 +349,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -373,12 +371,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained, - Children = new Controls + Children = { new Button { Name = "Button1" }, new Button { Name = "Button2" }, @@ -387,7 +385,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -410,12 +408,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once, - Children = new Controls + Children = { new Button { Name = "Button1" }, (current = new Button { Name = "Button2" }), @@ -424,7 +422,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { (next = new Button { Name = "Button4" }), new Button { Name = "Button5" }, @@ -448,12 +446,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { (container = new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once, - Children = new Controls + Children = { new Button { Name = "Button1" }, (next = new Button { Name = "Button2" }), @@ -462,7 +460,7 @@ namespace Avalonia.Input.UnitTests }), new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -487,12 +485,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.None, - Children = new Controls + Children = { new Button { Name = "Button1" }, (current = new Button { Name = "Button2" }), @@ -501,7 +499,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { (next = new Button { Name = "Button4" }), new Button { Name = "Button5" }, @@ -525,12 +523,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { (container = new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.None, - Children = new Controls + Children = { new Button { Name = "Button1" }, new Button { Name = "Button2" }, @@ -539,7 +537,7 @@ namespace Avalonia.Input.UnitTests }), new StackPanel { - Children = new Controls + Children = { (next = new Button { Name = "Button4" }), new Button { Name = "Button5" }, @@ -564,11 +562,11 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button1" }, (next = new Button { Name = "Button2" }), @@ -577,7 +575,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -600,11 +598,11 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button1" }, new Button { Name = "Button2" }, @@ -613,7 +611,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { (current = new Button { Name = "Button4" }), new Button { Name = "Button5" }, @@ -636,11 +634,11 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button1" }, new Button { Name = "Button2" }, @@ -664,15 +662,15 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button1" }, new Button { Name = "Button2" }, @@ -683,7 +681,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { (current = new Button { Name = "Button4" }), new Button { Name = "Button5" }, @@ -706,15 +704,15 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { (current = new Button { Name = "Button1" }), new Button { Name = "Button2" }, @@ -725,7 +723,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -767,12 +765,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, - Children = new Controls + Children = { (next = new Button { Name = "Button1" }), (current = new Button { Name = "Button2" }), @@ -781,7 +779,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -804,12 +802,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, - Children = new Controls + Children = { (current = new Button { Name = "Button1" }), new Button { Name = "Button2" }, @@ -818,7 +816,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -841,12 +839,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained, - Children = new Controls + Children = { (next = new Button { Name = "Button1" }), (current = new Button { Name = "Button2" }), @@ -855,7 +853,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -877,12 +875,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained, - Children = new Controls + Children = { (current = new Button { Name = "Button1" }), new Button { Name = "Button2" }, @@ -891,7 +889,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button4" }, new Button { Name = "Button5" }, @@ -914,11 +912,11 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { - Children = new Controls + Children = { new Button { Name = "Button1" }, new Button { Name = "Button2" }, @@ -928,7 +926,7 @@ namespace Avalonia.Input.UnitTests new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once, - Children = new Controls + Children = { new Button { Name = "Button4" }, (current = new Button { Name = "Button5" }), @@ -952,12 +950,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { (container = new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once, - Children = new Controls + Children = { new Button { Name = "Button1" }, (next = new Button { Name = "Button2" }), @@ -966,7 +964,7 @@ namespace Avalonia.Input.UnitTests }), new StackPanel { - Children = new Controls + Children = { (current = new Button { Name = "Button4" }), new Button { Name = "Button5" }, @@ -991,12 +989,12 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { - Children = new Controls + Children = { new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once, - Children = new Controls + Children = { (next = new Button { Name = "Button1" }), new Button { Name = "Button2" }, @@ -1005,7 +1003,7 @@ namespace Avalonia.Input.UnitTests }, new StackPanel { - Children = new Controls + Children = { (current = new Button { Name = "Button4" }), new Button { Name = "Button5" }, @@ -1028,7 +1026,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained, - Children = new Controls + Children = { (current = new Decorator { diff --git a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs index 361e7678be..4fce3fec0e 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs @@ -275,10 +275,10 @@ namespace Avalonia.Layout.UnitTests { Child = panel = new StackPanel { - Children = new Controls.Controls - { - (border = new Border()) - } + Children = + { + (border = new Border()) + } } }; diff --git a/tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs b/tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs index a5414f1e8c..b3e983036d 100644 --- a/tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs +++ b/tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs @@ -23,7 +23,7 @@ namespace Avalonia.Markup.UnitTests { Child = new StackPanel { - Children = new Controls.Controls + Children = { (target = new TextBlock { Name = "target" }), (relativeTo = new TextBlock { Name = "start" }), @@ -49,7 +49,7 @@ namespace Avalonia.Markup.UnitTests { Child = (panel = new StackPanel { - Children = new Controls.Controls + Children = { (relativeTo = new TextBlock { @@ -84,7 +84,7 @@ namespace Avalonia.Markup.UnitTests { Child = panel = new StackPanel { - Children = new Controls.Controls + Children = { (target = new TextBlock { Name = "target" }), (relativeTo = new TextBlock { Name = "start" }), @@ -114,7 +114,7 @@ namespace Avalonia.Markup.UnitTests { Child = new StackPanel { - Children = new Controls.Controls + Children = { (relativeTo = new TextBlock { @@ -129,7 +129,7 @@ namespace Avalonia.Markup.UnitTests { Child = new StackPanel { - Children = new Controls.Controls + Children = { (target2 = new TextBlock { Name = "target" }), } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index eb6dc8b5e5..a3e4ad1418 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -21,6 +21,7 @@ + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs index 9a08073920..71c5385c23 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs @@ -338,6 +338,145 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data Assert.Equal("foo", target.Content); } + [Fact] + public void StyledProperty_SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values() + { + var viewModel = new TestStackOverflowViewModel() + { + Value = 50 + }; + + var target = new StyledPropertyClass(); + + target.Bind(StyledPropertyClass.DoubleValueProperty, + new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel }); + + var child = new StyledPropertyClass(); + + child.Bind(StyledPropertyClass.DoubleValueProperty, + new Binding("DoubleValue") + { + Mode = BindingMode.TwoWay, + Source = target + }); + + Assert.Equal(1, viewModel.SetterInvokedCount); + + //here in real life stack overflow exception is thrown issue #855 and #824 + target.DoubleValue = 51.001; + + Assert.Equal(2, viewModel.SetterInvokedCount); + + double expected = 51; + + Assert.Equal(expected, viewModel.Value); + Assert.Equal(expected, target.DoubleValue); + Assert.Equal(expected, child.DoubleValue); + } + + [Fact] + public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values() + { + var viewModel = new TestStackOverflowViewModel() + { + Value = 50 + }; + + var target = new DirectPropertyClass(); + + target.Bind(DirectPropertyClass.DoubleValueProperty, new Binding("Value") + { + Mode = BindingMode.TwoWay, + Source = viewModel + }); + + var child = new DirectPropertyClass(); + + child.Bind(DirectPropertyClass.DoubleValueProperty, + new Binding("DoubleValue") + { + Mode = BindingMode.TwoWay, + Source = target + }); + + Assert.Equal(1, viewModel.SetterInvokedCount); + + //here in real life stack overflow exception is thrown issue #855 and #824 + target.DoubleValue = 51.001; + + Assert.Equal(2, viewModel.SetterInvokedCount); + + double expected = 51; + + Assert.Equal(expected, viewModel.Value); + Assert.Equal(expected, target.DoubleValue); + Assert.Equal(expected, child.DoubleValue); + } + + private class StyledPropertyClass : AvaloniaObject + { + public static readonly StyledProperty DoubleValueProperty = + AvaloniaProperty.Register(nameof(DoubleValue)); + + public double DoubleValue + { + get { return GetValue(DoubleValueProperty); } + set { SetValue(DoubleValueProperty, value); } + } + } + + private class DirectPropertyClass : AvaloniaObject + { + public static readonly DirectProperty DoubleValueProperty = + AvaloniaProperty.RegisterDirect( + nameof(DoubleValue), + o => o.DoubleValue, + (o, v) => o.DoubleValue = v); + + private double _doubleValue; + public double DoubleValue + { + get { return _doubleValue; } + set { SetAndRaise(DoubleValueProperty, ref _doubleValue, value); } + } + } + + private class TestStackOverflowViewModel : INotifyPropertyChanged + { + public int SetterInvokedCount { get; private set; } + + public const int MaxInvokedCount = 1000; + + private double _value; + + public event PropertyChangedEventHandler PropertyChanged; + + public double Value + { + get { return _value; } + set + { + if (_value != value) + { + SetterInvokedCount++; + if (SetterInvokedCount < MaxInvokedCount) + { + _value = (int)value; + if (_value > 75) _value = 75; + if (_value < 25) _value = 25; + } + else + { + _value = value; + } + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value))); + } + } + } + } + + [Fact] public void Binding_With_Null_Path_Works() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs index 40a4a6f0b3..d582964987 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs @@ -18,7 +18,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data { Child = new StackPanel { - Children = new Controls.Controls + Children = { new TextBlock { @@ -54,7 +54,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data { Child = new StackPanel { - Children = new Controls.Controls + Children = { (source = new TextBlock { @@ -89,7 +89,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data { Child = stackPanel = new StackPanel { - Children = new Controls.Controls + Children = { (target = new TextBlock { @@ -126,7 +126,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data { Child = stackPanel = new StackPanel { - Children = new Controls.Controls + Children = { (target = new ContentControl { diff --git a/tests/Avalonia.RenderTests/GeometryClippingTests.cs b/tests/Avalonia.RenderTests/GeometryClippingTests.cs index 01af638e6d..7a01c308a4 100644 --- a/tests/Avalonia.RenderTests/GeometryClippingTests.cs +++ b/tests/Avalonia.RenderTests/GeometryClippingTests.cs @@ -29,7 +29,7 @@ namespace Avalonia.Direct2D1.RenderTests Clip = StreamGeometry.Parse("F1 M 0,0 H 76 V 76 Z"), Width = 76, Height = 76, - Children = new Avalonia.Controls.Controls + Children = { new Path { diff --git a/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs b/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs index 60589f6a00..099b022862 100644 --- a/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs @@ -33,7 +33,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media { return new Panel { - Children = new Avalonia.Controls.Controls + Children = { new Image { diff --git a/tests/Avalonia.RenderTests/OpacityMaskTests.cs b/tests/Avalonia.RenderTests/OpacityMaskTests.cs index b53d9a70b1..1934654c26 100644 --- a/tests/Avalonia.RenderTests/OpacityMaskTests.cs +++ b/tests/Avalonia.RenderTests/OpacityMaskTests.cs @@ -37,7 +37,7 @@ namespace Avalonia.Direct2D1.RenderTests }, Width = 76, Height = 76, - Children = new Avalonia.Controls.Controls + Children = { new Path { diff --git a/tests/Avalonia.RenderTests/SVGPathTests.cs b/tests/Avalonia.RenderTests/SVGPathTests.cs index d1ed0ae1cf..3d0275c072 100644 --- a/tests/Avalonia.RenderTests/SVGPathTests.cs +++ b/tests/Avalonia.RenderTests/SVGPathTests.cs @@ -30,7 +30,7 @@ namespace Avalonia.Direct2D1.RenderTests Background = Brushes.Yellow, Width = 76, Height = 76, - Children = new Avalonia.Controls.Controls + Children = { new Path { diff --git a/tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs b/tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs index bcb5250be6..b87e72516b 100644 --- a/tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs +++ b/tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs @@ -21,7 +21,7 @@ namespace Avalonia.Visuals.UnitTests Width = 100, Height = 100, ClipToBounds = true, - Children = new Controls.Controls + Children = { (target = new TestControl { @@ -47,7 +47,7 @@ namespace Avalonia.Visuals.UnitTests Width = 100, Height = 100, ClipToBounds = true, - Children = new Controls.Controls + Children = { (target = new TestControl { @@ -74,7 +74,7 @@ namespace Avalonia.Visuals.UnitTests Width = 100, Height = 100, ClipToBounds = true, - Children = new Controls.Controls + Children = { new Canvas { @@ -82,7 +82,7 @@ namespace Avalonia.Visuals.UnitTests Height = 100, [Canvas.LeftProperty] = 50, [Canvas.TopProperty] = 50, - Children = new Controls.Controls + Children = { (target = new TestControl { @@ -111,7 +111,7 @@ namespace Avalonia.Visuals.UnitTests Width = 100, Height = 100, ClipToBounds = true, - Children = new Controls.Controls + Children = { (target = new TestControl { @@ -138,7 +138,7 @@ namespace Avalonia.Visuals.UnitTests Width = 100, Height = 100, ClipToBounds = true, - Children = new Controls.Controls + Children = { new Border { diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs index 9e2f1fc293..3a9e45a02b 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs @@ -154,7 +154,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering { Width = 200, Height = 200, - Children = new Controls.Controls + Children = { new Border { @@ -198,7 +198,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering { Width = 200, Height = 200, - Children = new Controls.Controls + Children = { new Border { @@ -255,7 +255,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering Height = 200, Background = Brushes.Red, ClipToBounds = false, - Children = new Controls.Controls + Children = { new Border { @@ -303,7 +303,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering Width = 100, Height = 200, Background = Brushes.Red, - Children = new Controls.Controls + Children = { new Panel() { @@ -312,7 +312,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering Background = Brushes.Red, Margin = new Thickness(0, 100, 0, 0), ClipToBounds = true, - Children = new Controls.Controls + Children = { (target = new Border() { @@ -354,7 +354,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering Width = 100, Height = 200, Background = Brushes.Red, - Children = new Controls.Controls + Children = { (target = new Border() { @@ -374,7 +374,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering { Content = new StackPanel() { - Children = new Controls.Controls + Children = { (item1 = new Border() { diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs index c8a19a9f46..9a1d8cb59c 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs @@ -126,7 +126,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering { Width = 200, Height = 200, - Children = new Controls.Controls + Children = { new Border { @@ -171,7 +171,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering { Width = 200, Height = 200, - Children = new Controls.Controls + Children = { new Border { @@ -238,7 +238,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering Height = 200, Background = Brushes.Red, ClipToBounds = false, - Children = new Controls.Controls + Children = { new Border { @@ -287,7 +287,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering Width = 100, Height = 200, Background = Brushes.Red, - Children = new Controls.Controls + Children = { new Panel() { @@ -296,7 +296,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering Background = Brushes.Red, Margin = new Thickness(0, 100, 0, 0), ClipToBounds = true, - Children = new Controls.Controls + Children = { (target = new Border() { @@ -339,7 +339,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering Width = 100, Height = 200, Background = Brushes.Red, - Children = new Controls.Controls + Children = { (target = new Border() { @@ -359,7 +359,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering { Content = new StackPanel() { - Children = new Controls.Controls + Children = { (item1 = new Border() {