diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj
index 97dfd1230d..6674e45fd1 100644
--- a/src/Avalonia.Base/Avalonia.Base.csproj
+++ b/src/Avalonia.Base/Avalonia.Base.csproj
@@ -43,7 +43,7 @@
Properties\SharedAssemblyInfo.cs
-
+
diff --git a/src/Avalonia.Base/Data/BindingBrokenException.cs b/src/Avalonia.Base/Data/BindingBrokenException.cs
deleted file mode 100644
index 057629edb8..0000000000
--- a/src/Avalonia.Base/Data/BindingBrokenException.cs
+++ /dev/null
@@ -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
-{
- ///
- /// An exception returned through signalling that a
- /// requested binding expression could not be evaluated.
- ///
- public class BindingBrokenException : Exception
- {
- }
-}
diff --git a/src/Avalonia.Base/Data/BindingChainNullException.cs b/src/Avalonia.Base/Data/BindingChainNullException.cs
new file mode 100644
index 0000000000..0e50a36d8a
--- /dev/null
+++ b/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
+{
+ ///
+ /// An exception returned through signalling that a
+ /// requested binding expression could not be evaluated because of a null in one of the links
+ /// of the binding chain.
+ ///
+ public class BindingChainNullException : Exception
+ {
+ private string _message;
+
+ ///
+ /// Initalizes a new instance of the class.
+ ///
+ public BindingChainNullException()
+ {
+ }
+
+ ///
+ /// Initalizes a new instance of the class.
+ ///
+ public BindingChainNullException(string message)
+ {
+ _message = message;
+ }
+
+ ///
+ /// Initalizes a new instance of the class.
+ ///
+ /// The expression.
+ ///
+ /// The point in the expression at which the null was encountered.
+ ///
+ public BindingChainNullException(string expression, string expressionNullPoint)
+ {
+ Expression = expression;
+ ExpressionNullPoint = expressionNullPoint;
+ }
+
+ ///
+ /// Gets the expression that could not be evaluated.
+ ///
+ public string Expression { get; protected set; }
+
+ ///
+ /// Gets the point in the expression at which the null was encountered.
+ ///
+ public string ExpressionNullPoint { get; protected set; }
+
+ ///
+ 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.";
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Data/BindingNotification.cs b/src/Avalonia.Base/Data/BindingNotification.cs
index 0f587b969e..ecaf59e174 100644
--- a/src/Avalonia.Base/Data/BindingNotification.cs
+++ b/src/Avalonia.Base/Data/BindingNotification.cs
@@ -267,7 +267,7 @@ namespace Avalonia.Data
case BindingErrorType.None:
return $"{{Value: {Value}}}";
default:
- return HasValue ?
+ return HasValue ?
$"{{{ErrorType}: {Error}, Fallback: {Value}}}" :
$"{{{ErrorType}: {Error}}}";
}
diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index dbea94f9a4..2c43f8f97e 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/src/Avalonia.Controls/TextBox.cs
@@ -499,7 +499,7 @@ namespace Avalonia.Controls
var exceptions = aggregate == null ?
(IEnumerable)new[] { exception } :
aggregate.InnerExceptions;
- var filtered = exceptions.Where(x => !(x is BindingBrokenException)).ToList();
+ var filtered = exceptions.Where(x => !(x is BindingChainNullException)).ToList();
if (filtered.Count > 0)
{
diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs b/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
index 6b8b1282e5..832a25be27 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
@@ -238,10 +238,12 @@ namespace Avalonia.Markup.Xaml.Data
{
Contract.Requires(target != null);
+ var description = $"#{elementName}.{path}";
var result = new ExpressionObserver(
ControlLocator.Track(target, elementName),
path,
- false);
+ false,
+ description);
return result;
}
diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
index 334add960e..9edfd2957d 100644
--- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
+++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
@@ -41,7 +41,7 @@
Properties\SharedAssemblyInfo.cs
-
+
diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionNode.cs b/src/Markup/Avalonia.Markup/Data/ExpressionNode.cs
index d6f2d66adf..0e7777b732 100644
--- a/src/Markup/Avalonia.Markup/Data/ExpressionNode.cs
+++ b/src/Markup/Avalonia.Markup/Data/ExpressionNode.cs
@@ -92,8 +92,8 @@ namespace Avalonia.Markup.Data
protected virtual void NextValueChanged(object value)
{
- var bindingBroken = BindingNotification.ExtractError(value) as MarkupBindingBrokenException;
- bindingBroken?.Nodes.Add(Description);
+ var bindingBroken = BindingNotification.ExtractError(value) as MarkupBindingChainNullException;
+ bindingBroken?.AddNode(Description);
_observer.OnNext(value);
}
@@ -181,7 +181,7 @@ namespace Avalonia.Markup.Data
private BindingNotification TargetNullNotification()
{
return new BindingNotification(
- new MarkupBindingBrokenException(this),
+ new MarkupBindingChainNullException(),
BindingErrorType.Error,
AvaloniaProperty.UnsetValue);
}
diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs b/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs
index f3f175e04c..819949b7b9 100644
--- a/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs
+++ b/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs
@@ -63,7 +63,14 @@ namespace Avalonia.Markup.Data
/// The root object.
/// The expression.
/// Whether data validation should be enabled.
- public ExpressionObserver(object root, string expression, bool enableDataValidation = false)
+ ///
+ /// A description of the expression. If null, will be used.
+ ///
+ public ExpressionObserver(
+ object root,
+ string expression,
+ bool enableDataValidation = false,
+ string description = null)
{
Contract.Requires(expression != null);
@@ -73,6 +80,7 @@ namespace Avalonia.Markup.Data
}
Expression = expression;
+ Description = description ?? expression;
_node = Parse(expression, enableDataValidation);
_root = new WeakReference(root);
}
@@ -83,15 +91,20 @@ namespace Avalonia.Markup.Data
/// An observable which provides the root object.
/// The expression.
/// Whether data validation should be enabled.
+ ///
+ /// A description of the expression. If null, will be used.
+ ///
public ExpressionObserver(
IObservable