Browse Source

Try to improve logged binding errors.

- Don't log an error when the target for the root ExpressionNode is
null. This is usually because the `DataContext` hasn't been set up yet
and it spewed a load of useless error messages.
- Add a Description field to `ExpressionObserver` that can be used in
the case of e.g. #control bindings to record the whole expression (with
the "#control" part) rather than just the part tracked by the
`ExpressionObserver`.
pull/691/head
Steven Kirk 10 years ago
parent
commit
7cb001801a
  1. 2
      src/Avalonia.Base/Avalonia.Base.csproj
  2. 15
      src/Avalonia.Base/Data/BindingBrokenException.cs
  3. 85
      src/Avalonia.Base/Data/BindingChainNullException.cs
  4. 2
      src/Avalonia.Base/Data/BindingNotification.cs
  5. 2
      src/Avalonia.Controls/TextBox.cs
  6. 4
      src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
  7. 2
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  8. 6
      src/Markup/Avalonia.Markup/Data/ExpressionNode.cs
  9. 50
      src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs
  10. 65
      src/Markup/Avalonia.Markup/Data/MarkupBindingBrokenException.cs
  11. 33
      src/Markup/Avalonia.Markup/Data/MarkupBindingChainNullException.cs
  12. 2
      tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_DataValidation.cs
  13. 104
      tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs
  14. 3
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs

2
src/Avalonia.Base/Avalonia.Base.csproj

@ -43,7 +43,7 @@
<Compile Include="..\Shared\SharedAssemblyInfo.cs"> <Compile Include="..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link> <Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile> </Compile>
<Compile Include="Data\BindingBrokenException.cs" /> <Compile Include="Data\BindingChainNullException.cs" />
<Compile Include="Data\BindingNotification.cs" /> <Compile Include="Data\BindingNotification.cs" />
<Compile Include="Data\IndexerBinding.cs" /> <Compile Include="Data\IndexerBinding.cs" />
<Compile Include="Diagnostics\INotifyCollectionChangedDebug.cs" /> <Compile Include="Diagnostics\INotifyCollectionChangedDebug.cs" />

15
src/Avalonia.Base/Data/BindingBrokenException.cs

@ -1,15 +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;
namespace Avalonia.Data
{
/// <summary>
/// An exception returned through <see cref="BindingNotification"/> signalling that a
/// requested binding expression could not be evaluated.
/// </summary>
public class BindingBrokenException : Exception
{
}
}

85
src/Avalonia.Base/Data/BindingChainNullException.cs

@ -0,0 +1,85 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Data
{
/// <summary>
/// An exception returned through <see cref="BindingNotification"/> signalling that a
/// requested binding expression could not be evaluated because of a null in one of the links
/// of the binding chain.
/// </summary>
public class BindingChainNullException : Exception
{
private string _message;
/// <summary>
/// Initalizes a new instance of the <see cref="BindingChainNullException"/> class.
/// </summary>
public BindingChainNullException()
{
}
/// <summary>
/// Initalizes a new instance of the <see cref="BindingChainNullException"/> class.
/// </summary>
public BindingChainNullException(string message)
{
_message = message;
}
/// <summary>
/// Initalizes a new instance of the <see cref="BindingChainNullException"/> class.
/// </summary>
/// <param name="expression">The expression.</param>
/// <param name="expressionNullPoint">
/// The point in the expression at which the null was encountered.
/// </param>
public BindingChainNullException(string expression, string expressionNullPoint)
{
Expression = expression;
ExpressionNullPoint = expressionNullPoint;
}
/// <summary>
/// Gets the expression that could not be evaluated.
/// </summary>
public string Expression { get; protected set; }
/// <summary>
/// Gets the point in the expression at which the null was encountered.
/// </summary>
public string ExpressionNullPoint { get; protected set; }
/// <inheritdoc/>
public override string Message
{
get
{
if (_message == null)
{
_message = BuildMessage();
}
return _message;
}
}
private string BuildMessage()
{
if (Expression != null && ExpressionNullPoint != null)
{
return $"'{ExpressionNullPoint}' is null in expression '{Expression}'.";
}
else if (ExpressionNullPoint != null)
{
return $"'{ExpressionNullPoint}' is null in expression.";
}
else
{
return "Null encountered in binding expression.";
}
}
}
}

2
src/Avalonia.Base/Data/BindingNotification.cs

@ -267,7 +267,7 @@ namespace Avalonia.Data
case BindingErrorType.None: case BindingErrorType.None:
return $"{{Value: {Value}}}"; return $"{{Value: {Value}}}";
default: default:
return HasValue ? return HasValue ?
$"{{{ErrorType}: {Error}, Fallback: {Value}}}" : $"{{{ErrorType}: {Error}, Fallback: {Value}}}" :
$"{{{ErrorType}: {Error}}}"; $"{{{ErrorType}: {Error}}}";
} }

2
src/Avalonia.Controls/TextBox.cs

@ -499,7 +499,7 @@ namespace Avalonia.Controls
var exceptions = aggregate == null ? var exceptions = aggregate == null ?
(IEnumerable<Exception>)new[] { exception } : (IEnumerable<Exception>)new[] { exception } :
aggregate.InnerExceptions; aggregate.InnerExceptions;
var filtered = exceptions.Where(x => !(x is BindingBrokenException)).ToList(); var filtered = exceptions.Where(x => !(x is BindingChainNullException)).ToList();
if (filtered.Count > 0) if (filtered.Count > 0)
{ {

4
src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs

@ -238,10 +238,12 @@ namespace Avalonia.Markup.Xaml.Data
{ {
Contract.Requires<ArgumentNullException>(target != null); Contract.Requires<ArgumentNullException>(target != null);
var description = $"#{elementName}.{path}";
var result = new ExpressionObserver( var result = new ExpressionObserver(
ControlLocator.Track(target, elementName), ControlLocator.Track(target, elementName),
path, path,
false); false,
description);
return result; return result;
} }

2
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@ -41,7 +41,7 @@
<Compile Include="..\..\Shared\SharedAssemblyInfo.cs"> <Compile Include="..\..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link> <Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile> </Compile>
<Compile Include="Data\MarkupBindingBrokenException.cs" /> <Compile Include="Data\MarkupBindingChainNullException.cs" />
<Compile Include="Data\CommonPropertyNames.cs" /> <Compile Include="Data\CommonPropertyNames.cs" />
<Compile Include="Data\EmptyExpressionNode.cs" /> <Compile Include="Data\EmptyExpressionNode.cs" />
<Compile Include="Data\ExpressionNodeBuilder.cs" /> <Compile Include="Data\ExpressionNodeBuilder.cs" />

6
src/Markup/Avalonia.Markup/Data/ExpressionNode.cs

@ -92,8 +92,8 @@ namespace Avalonia.Markup.Data
protected virtual void NextValueChanged(object value) protected virtual void NextValueChanged(object value)
{ {
var bindingBroken = BindingNotification.ExtractError(value) as MarkupBindingBrokenException; var bindingBroken = BindingNotification.ExtractError(value) as MarkupBindingChainNullException;
bindingBroken?.Nodes.Add(Description); bindingBroken?.AddNode(Description);
_observer.OnNext(value); _observer.OnNext(value);
} }
@ -181,7 +181,7 @@ namespace Avalonia.Markup.Data
private BindingNotification TargetNullNotification() private BindingNotification TargetNullNotification()
{ {
return new BindingNotification( return new BindingNotification(
new MarkupBindingBrokenException(this), new MarkupBindingChainNullException(),
BindingErrorType.Error, BindingErrorType.Error,
AvaloniaProperty.UnsetValue); AvaloniaProperty.UnsetValue);
} }

50
src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs

@ -63,7 +63,14 @@ namespace Avalonia.Markup.Data
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="expression">The expression.</param> /// <param name="expression">The expression.</param>
/// <param name="enableDataValidation">Whether data validation should be enabled.</param> /// <param name="enableDataValidation">Whether data validation should be enabled.</param>
public ExpressionObserver(object root, string expression, bool enableDataValidation = false) /// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/> will be used.
/// </param>
public ExpressionObserver(
object root,
string expression,
bool enableDataValidation = false,
string description = null)
{ {
Contract.Requires<ArgumentNullException>(expression != null); Contract.Requires<ArgumentNullException>(expression != null);
@ -73,6 +80,7 @@ namespace Avalonia.Markup.Data
} }
Expression = expression; Expression = expression;
Description = description ?? expression;
_node = Parse(expression, enableDataValidation); _node = Parse(expression, enableDataValidation);
_root = new WeakReference(root); _root = new WeakReference(root);
} }
@ -83,15 +91,20 @@ namespace Avalonia.Markup.Data
/// <param name="rootObservable">An observable which provides the root object.</param> /// <param name="rootObservable">An observable which provides the root object.</param>
/// <param name="expression">The expression.</param> /// <param name="expression">The expression.</param>
/// <param name="enableDataValidation">Whether data validation should be enabled.</param> /// <param name="enableDataValidation">Whether data validation should be enabled.</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/> will be used.
/// </param>
public ExpressionObserver( public ExpressionObserver(
IObservable<object> rootObservable, IObservable<object> rootObservable,
string expression, string expression,
bool enableDataValidation = false) bool enableDataValidation = false,
string description = null)
{ {
Contract.Requires<ArgumentNullException>(rootObservable != null); Contract.Requires<ArgumentNullException>(rootObservable != null);
Contract.Requires<ArgumentNullException>(expression != null); Contract.Requires<ArgumentNullException>(expression != null);
Expression = expression; Expression = expression;
Description = description ?? expression;
_node = Parse(expression, enableDataValidation); _node = Parse(expression, enableDataValidation);
_finished = new Subject<Unit>(); _finished = new Subject<Unit>();
_root = rootObservable; _root = rootObservable;
@ -104,17 +117,22 @@ namespace Avalonia.Markup.Data
/// <param name="expression">The expression.</param> /// <param name="expression">The expression.</param>
/// <param name="update">An observable which triggers a re-read of the getter.</param> /// <param name="update">An observable which triggers a re-read of the getter.</param>
/// <param name="enableDataValidation">Whether data validation should be enabled.</param> /// <param name="enableDataValidation">Whether data validation should be enabled.</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/> will be used.
/// </param>
public ExpressionObserver( public ExpressionObserver(
Func<object> rootGetter, Func<object> rootGetter,
string expression, string expression,
IObservable<Unit> update, IObservable<Unit> update,
bool enableDataValidation = false) bool enableDataValidation = false,
string description = null)
{ {
Contract.Requires<ArgumentNullException>(rootGetter != null); Contract.Requires<ArgumentNullException>(rootGetter != null);
Contract.Requires<ArgumentNullException>(expression != null); Contract.Requires<ArgumentNullException>(expression != null);
Contract.Requires<ArgumentNullException>(update != null); Contract.Requires<ArgumentNullException>(update != null);
Expression = expression; Expression = expression;
Description = description ?? expression;
_node = Parse(expression, enableDataValidation); _node = Parse(expression, enableDataValidation);
_finished = new Subject<Unit>(); _finished = new Subject<Unit>();
@ -138,6 +156,11 @@ namespace Avalonia.Markup.Data
return (Leaf as PropertyAccessorNode)?.SetTargetValue(value, priority) ?? false; return (Leaf as PropertyAccessorNode)?.SetTargetValue(value, priority) ?? false;
} }
/// <summary>
/// Gets a description of the expression being observed.
/// </summary>
public string Description { get; }
/// <summary> /// <summary>
/// Gets the expression being observed. /// Gets the expression being observed.
/// </summary> /// </summary>
@ -149,9 +172,6 @@ namespace Avalonia.Markup.Data
/// </summary> /// </summary>
public Type ResultType => (Leaf as PropertyAccessorNode)?.PropertyType; public Type ResultType => (Leaf as PropertyAccessorNode)?.PropertyType;
/// <inheritdoc/>
string IDescription.Description => Expression;
/// <summary> /// <summary>
/// Gets the leaf node. /// Gets the leaf node.
/// </summary> /// </summary>
@ -215,15 +235,23 @@ namespace Avalonia.Markup.Data
} }
else else
{ {
var notification = o as BindingNotification; var broken = BindingNotification.ExtractError(o) as MarkupBindingChainNullException;
var broken = notification.Error as MarkupBindingBrokenException;
if (broken != null) if (broken != null)
{ {
broken.Expression = Expression; // We've received notification of a broken expression due to a null value
// somewhere in the chain. If this null value occurs at the first node then we
// ignore it, as its likely that e.g. the DataContext has not yet been set up.
if (broken.HasNodes)
{
broken.Commit(Description);
}
else
{
o = AvaloniaProperty.UnsetValue;
}
} }
return o;
return notification;
} }
} }

65
src/Markup/Avalonia.Markup/Data/MarkupBindingBrokenException.cs

@ -1,65 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Data;
namespace Avalonia.Markup.Data
{
public class MarkupBindingBrokenException : BindingBrokenException
{
private string _message;
public MarkupBindingBrokenException()
{
}
public MarkupBindingBrokenException(string message)
{
_message = message;
}
internal MarkupBindingBrokenException(ExpressionNode node)
{
Nodes.Add(node.Description);
}
public override string Message
{
get
{
if (_message != null)
{
return _message;
}
else
{
return _message = BuildMessage();
}
}
}
internal string Expression { get; set; }
internal IList<string> Nodes { get; } = new List<string>();
private string BuildMessage()
{
if (Nodes.Count == 0)
{
return "The binding chain was broken.";
}
else if (Nodes.Count == 1)
{
return $"'{Nodes[0]}' is null in expression '{Expression}'.";
}
else
{
var brokenPath = string.Join(".", Nodes.Skip(1).Reverse())
.Replace(".!", "!")
.Replace(".[", "[");
return $"'{brokenPath}' is null in expression '{Expression}'.";
}
}
}
}

33
src/Markup/Avalonia.Markup/Data/MarkupBindingChainNullException.cs

@ -0,0 +1,33 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Data;
namespace Avalonia.Markup.Data
{
internal class MarkupBindingChainNullException : BindingChainNullException
{
private IList<string> _nodes = new List<string>();
public MarkupBindingChainNullException()
{
}
public MarkupBindingChainNullException(string expression, string expressionNullPoint)
: base(expression, expressionNullPoint)
{
_nodes = null;
}
public bool HasNodes => _nodes.Count > 0;
public void AddNode(string node) => _nodes.Add(node);
public void Commit(string expression)
{
Expression = expression;
ExpressionNullPoint = string.Join(".", _nodes.Reverse())
.Replace(".!", "!")
.Replace(".[", "[");
_nodes = null;
}
}
}

2
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_DataValidation.cs

@ -143,7 +143,7 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal(new[] Assert.Equal(new[]
{ {
new BindingNotification( new BindingNotification(
new MarkupBindingBrokenException("'Inner' is null in expression 'Inner.MustBePositive'."), new MarkupBindingChainNullException("Inner.MustBePositive", "Inner"),
BindingErrorType.Error, BindingErrorType.Error,
AvaloniaProperty.UnsetValue), AvaloniaProperty.UnsetValue),
}, result); }, result);

104
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs

@ -58,63 +58,43 @@ namespace Avalonia.Markup.UnitTests.Data
} }
[Fact] [Fact]
public async void Should_Return_BindingNotification_Error_For_Root_Null() public async void Should_Return_UnsetValue_For_Root_Null()
{ {
var data = new Class3 { Foo = "foo" }; var data = new Class3 { Foo = "foo" };
var target = new ExpressionObserver(default(object), "Foo"); var target = new ExpressionObserver(default(object), "Foo");
var result = await target.Take(1); var result = await target.Take(1);
Assert.Equal( Assert.Equal(AvaloniaProperty.UnsetValue, result);
new BindingNotification(
new MarkupBindingBrokenException("'Foo' is null in expression 'Foo'."),
BindingErrorType.Error,
AvaloniaProperty.UnsetValue),
result);
} }
[Fact] [Fact]
public async void Should_Return_BindingNotification_Error_For_Root_UnsetValue() public async void Should_Return_UnsetValue_For_Root_UnsetValue()
{ {
var data = new Class3 { Foo = "foo" }; var data = new Class3 { Foo = "foo" };
var target = new ExpressionObserver(AvaloniaProperty.UnsetValue, "Foo"); var target = new ExpressionObserver(AvaloniaProperty.UnsetValue, "Foo");
var result = await target.Take(1); var result = await target.Take(1);
Assert.Equal( Assert.Equal(AvaloniaProperty.UnsetValue, result);
new BindingNotification(
new MarkupBindingBrokenException("'Foo' is null in expression 'Foo'."),
BindingErrorType.Error,
AvaloniaProperty.UnsetValue),
result);
} }
[Fact] [Fact]
public async void Should_Return_BindingNotification_Error_For_Observable_Root_Null() public async void Should_Return_UnsetValue_For_Observable_Root_Null()
{ {
var data = new Class3 { Foo = "foo" }; var data = new Class3 { Foo = "foo" };
var target = new ExpressionObserver(Observable.Return(default(object)), "Foo"); var target = new ExpressionObserver(Observable.Return(default(object)), "Foo");
var result = await target.Take(1); var result = await target.Take(1);
Assert.Equal( Assert.Equal(AvaloniaProperty.UnsetValue, result);
new BindingNotification(
new MarkupBindingBrokenException("'Foo' is null in expression 'Foo'."),
BindingErrorType.Error,
AvaloniaProperty.UnsetValue),
result);
} }
[Fact] [Fact]
public async void Should_Return_BindingNotification_Error_For_Observable_Root_UnsetValue() public async void Should_Return_UnsetValue_For_Observable_Root_UnsetValue()
{ {
var data = new Class3 { Foo = "foo" }; var data = new Class3 { Foo = "foo" };
var target = new ExpressionObserver(Observable.Return(AvaloniaProperty.UnsetValue), "Foo"); var target = new ExpressionObserver(Observable.Return(AvaloniaProperty.UnsetValue), "Foo");
var result = await target.Take(1); var result = await target.Take(1);
Assert.Equal( Assert.Equal(AvaloniaProperty.UnsetValue, result);
new BindingNotification(
new MarkupBindingBrokenException("'Foo' is null in expression 'Foo'."),
BindingErrorType.Error,
AvaloniaProperty.UnsetValue),
result);
} }
[Fact] [Fact]
@ -166,7 +146,7 @@ namespace Avalonia.Markup.UnitTests.Data
new[] new[]
{ {
new BindingNotification( new BindingNotification(
new MarkupBindingBrokenException("'Foo' is null in expression 'Foo.Bar.Baz'."), new MarkupBindingChainNullException("Foo.Bar.Baz", "Foo"),
BindingErrorType.Error, BindingErrorType.Error,
AvaloniaProperty.UnsetValue), AvaloniaProperty.UnsetValue),
}, },
@ -270,24 +250,34 @@ namespace Avalonia.Markup.UnitTests.Data
[Fact] [Fact]
public void Should_Track_Property_Chain_Breaking_With_Null_Then_Mending() public void Should_Track_Property_Chain_Breaking_With_Null_Then_Mending()
{ {
var data = new Class1 { Next = new Class2 { Bar = "bar" } }; var data = new Class1
var target = new ExpressionObserver(data, "Next.Bar"); {
Next = new Class2
{
Next = new Class2
{
Bar = "bar"
}
}
};
var target = new ExpressionObserver(data, "Next.Next.Bar");
var result = new List<object>(); var result = new List<object>();
var sub = target.Subscribe(x => result.Add(x)); var sub = target.Subscribe(x => result.Add(x));
var old = data.Next; var old = data.Next;
data.Next = null;
data.Next = new Class2 { Bar = "baz" }; data.Next = new Class2 { Bar = "baz" };
data.Next = old;
Assert.Equal( Assert.Equal(
new object[] new object[]
{ {
"bar", "bar",
new BindingNotification( new BindingNotification(
new MarkupBindingBrokenException("'Next' is null in expression 'Next.Bar'."), new MarkupBindingChainNullException("Next.Next.Bar", "Next.Next"),
BindingErrorType.Error, BindingErrorType.Error,
AvaloniaProperty.UnsetValue), AvaloniaProperty.UnsetValue),
"baz" "bar"
}, },
result); result);
@ -299,7 +289,7 @@ namespace Avalonia.Markup.UnitTests.Data
} }
[Fact] [Fact]
public void Should_Track_Property_Chain_Breaking_With_Object_Then_Mending() public void Should_Track_Property_Chain_Breaking_With_Missing_Member_Then_Mending()
{ {
var data = new Class1 { Next = new Class2 { Bar = "bar" } }; var data = new Class1 { Next = new Class2 { Bar = "bar" } };
var target = new ExpressionObserver(data, "Next.Bar"); var target = new ExpressionObserver(data, "Next.Bar");
@ -311,10 +301,16 @@ namespace Avalonia.Markup.UnitTests.Data
data.Next = breaking; data.Next = breaking;
data.Next = new Class2 { Bar = "baz" }; data.Next = new Class2 { Bar = "baz" };
Assert.Equal(3, result.Count); Assert.Equal(
Assert.Equal("bar", result[0]); new object[]
Assert.IsType<BindingNotification>(result[1]); {
Assert.Equal("baz", result[2]); "bar",
new BindingNotification(
new MissingMemberException("Could not find CLR property 'Bar' on 'Avalonia.Markup.UnitTests.Data.ExpressionObserverTests_Property+WithoutBar'"),
BindingErrorType.Error),
"baz",
},
result);
sub.Dispose(); sub.Dispose();
@ -475,20 +471,6 @@ namespace Avalonia.Markup.UnitTests.Data
} }
} }
[Fact]
public async void Should_Handle_Null_Root()
{
var target = new ExpressionObserver((object)null, "Foo");
var result = await target.Take(1);
Assert.Equal(
new BindingNotification(
new MarkupBindingBrokenException("'Foo' is null in expression 'Foo'."),
BindingErrorType.Error,
AvaloniaProperty.UnsetValue),
result);
}
[Fact] [Fact]
public void Can_Replace_Root() public void Can_Replace_Root()
{ {
@ -510,10 +492,7 @@ namespace Avalonia.Markup.UnitTests.Data
{ {
"foo", "foo",
"bar", "bar",
new BindingNotification( AvaloniaProperty.UnsetValue,
new MarkupBindingBrokenException("'Foo' is null in expression 'Foo'."),
BindingErrorType.Error,
AvaloniaProperty.UnsetValue),
}, },
result); result);
@ -580,6 +559,7 @@ namespace Avalonia.Markup.UnitTests.Data
private class Class2 : NotifyingBase, INext private class Class2 : NotifyingBase, INext
{ {
private string _bar; private string _bar;
private INext _next;
public string Bar public string Bar
{ {
@ -590,6 +570,16 @@ namespace Avalonia.Markup.UnitTests.Data
RaisePropertyChanged(nameof(Bar)); RaisePropertyChanged(nameof(Bar));
} }
} }
public INext Next
{
get { return _next; }
set
{
_next = value;
RaisePropertyChanged(nameof(Next));
}
}
} }
private class Class3 : Class1 private class Class3 : Class1

3
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs

@ -43,8 +43,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
pv.Length == 3 && pv.Length == 3 &&
pv[0] is ProgressBar && pv[0] is ProgressBar &&
object.ReferenceEquals(pv[1], ProgressBar.ValueProperty) && object.ReferenceEquals(pv[1], ProgressBar.ValueProperty) &&
(string)pv[2] == "'Value' is null in expression 'Value'. | " + (string)pv[2] == "Could not convert FallbackValue 'bar' to 'System.Double'")
"Could not convert FallbackValue 'bar' to 'System.Double'")
{ {
called = true; called = true;
} }

Loading…
Cancel
Save