|
|
@@ -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<ArgumentException>(() =>
|
|
|
+ Assert.Throws<ArgumentException>(() =>
|
|
|
target.SetValue(Class1.BarProperty, "newvalue"));
|
|
|
}
|
|
|
|
|
|
@@ -217,7 +218,7 @@ namespace Avalonia.Base.UnitTests
|
|
|
{
|
|
|
var target = new Class1();
|
|
|
|
|
|
- Assert.Throws<ArgumentException>(() =>
|
|
|
+ Assert.Throws<ArgumentException>(() =>
|
|
|
target.SetValue((AvaloniaProperty)Class1.BarProperty, "newvalue"));
|
|
|
}
|
|
|
|
|
|
@@ -227,7 +228,7 @@ namespace Avalonia.Base.UnitTests
|
|
|
var target = new Class1();
|
|
|
var source = new Subject<string>();
|
|
|
|
|
|
- Assert.Throws<ArgumentException>(() =>
|
|
|
+ Assert.Throws<ArgumentException>(() =>
|
|
|
target.Bind(Class1.BarProperty, source));
|
|
|
}
|
|
|
|
|
|
@@ -439,12 +440,49 @@ namespace Avalonia.Base.UnitTests
|
|
|
Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata<Class2>().DefaultBindingMode);
|
|
|
}
|
|
|
|
|
|
+ [Fact]
|
|
|
+ public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
|
|
|
+ {
|
|
|
+ var viewModel = new TestStackOverflowViewModel()
|
|
|
+ {
|
|
|
+ Value = 50
|
|
|
+ };
|
|
|
+
|
|
|
+ var target = new Class1();
|
|
|
+
|
|
|
+ //note: if the initialization of the child binding is here there is no stackoverflow!!!
|
|
|
+ //var child = new Class1()
|
|
|
+ //{
|
|
|
+ // [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty]
|
|
|
+ //};
|
|
|
+
|
|
|
+ target.Bind(Class1.DoubleValueProperty, new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel });
|
|
|
+
|
|
|
+ var child = new Class1()
|
|
|
+ {
|
|
|
+ [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty]
|
|
|
+ };
|
|
|
+
|
|
|
+ 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 Class1 : AvaloniaObject
|
|
|
{
|
|
|
public static readonly DirectProperty<Class1, string> FooProperty =
|
|
|
AvaloniaProperty.RegisterDirect<Class1, string>(
|
|
|
- "Foo",
|
|
|
- o => o.Foo,
|
|
|
+ "Foo",
|
|
|
+ o => o.Foo,
|
|
|
(o, v) => o.Foo = v,
|
|
|
unsetValue: "unset");
|
|
|
|
|
|
@@ -453,14 +491,21 @@ namespace Avalonia.Base.UnitTests
|
|
|
|
|
|
public static readonly DirectProperty<Class1, int> BazProperty =
|
|
|
AvaloniaProperty.RegisterDirect<Class1, int>(
|
|
|
- "Bar",
|
|
|
- o => o.Baz,
|
|
|
- (o,v) => o.Baz = v,
|
|
|
+ "Bar",
|
|
|
+ o => o.Baz,
|
|
|
+ (o, v) => o.Baz = v,
|
|
|
unsetValue: -1);
|
|
|
|
|
|
+ public static readonly DirectProperty<Class1, double> DoubleValueProperty =
|
|
|
+ AvaloniaProperty.RegisterDirect<Class1, double>(
|
|
|
+ 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 +523,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 +548,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)));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
-}
|
|
|
+}
|