Browse Source

Only log binding errors when attached to a tree.

Steven Kirk 6 years ago
parent
commit
6961d55e7a

+ 42 - 1
src/Avalonia.Base/AvaloniaObject.cs

@@ -415,6 +415,7 @@ namespace Avalonia
         
         internal void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification)
         {
+            LogIfError(property, notification);
             UpdateDataValidation(property, notification);
         }
 
@@ -446,6 +447,23 @@ namespace Avalonia
             });
         }
 
+        /// <summary>
+        /// Logs a binding error for a property.
+        /// </summary>
+        /// <param name="property">The property that the error occurred on.</param>
+        /// <param name="e">The binding error.</param>
+        protected internal virtual void LogBindingError(AvaloniaProperty property, Exception e)
+        {
+            Logger.Log(
+                LogEventLevel.Warning,
+                LogArea.Binding,
+                this,
+                "Error in binding to {Target}.{Property}: {Message}",
+                this,
+                property,
+                e.Message);
+        }
+
         /// <summary>
         /// Called to update the validation state for properties for which data validation is
         /// enabled.
@@ -647,7 +665,7 @@ namespace Avalonia
 
                 if (notification != null)
                 {
-                    notification.LogIfError(this, property);
+                    LogIfError(property, notification);
                     value = notification.Value;
                 }
 
@@ -779,6 +797,29 @@ namespace Avalonia
             return description?.Description ?? o.ToString();
         }
 
+        /// <summary>
+        /// Logs a mesage if the notification represents a binding error.
+        /// </summary>
+        /// <param name="property">The property being bound.</param>
+        /// <param name="notification">The binding notification.</param>
+        private void LogIfError(AvaloniaProperty property, BindingNotification notification)
+        {
+            if (notification.ErrorType == BindingErrorType.Error)
+            {
+                if (notification.Error is AggregateException aggregate)
+                {
+                    foreach (var inner in aggregate.InnerExceptions)
+                    {
+                        LogBindingError(property, inner);
+                    }
+                }
+                else
+                {
+                    LogBindingError(property, notification.Error);
+                }
+            }
+        }
+
         /// <summary>
         /// Logs a property set message.
         /// </summary>

+ 8 - 0
src/Avalonia.Base/IPriorityValueOwner.cs

@@ -1,6 +1,7 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using System;
 using Avalonia.Data;
 using Avalonia.Utilities;
 
@@ -28,6 +29,13 @@ namespace Avalonia
         /// <param name="notification">The notification.</param>
         void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification);
 
+        /// <summary>
+        /// Logs a binding error.
+        /// </summary>
+        /// <param name="property">The property the error occurred on.</param>
+        /// <param name="e">The binding error.</param>
+        void LogError(AvaloniaProperty property, Exception e);
+
         /// <summary>
         /// Ensures that the current thread is the UI thread.
         /// </summary>

+ 0 - 53
src/Avalonia.Base/Logging/LoggerExtensions.cs

@@ -1,53 +0,0 @@
-using System;
-using Avalonia.Data;
-
-namespace Avalonia.Logging
-{
-    internal static class LoggerExtensions
-    {
-        public static void LogIfError(
-            this BindingNotification notification,
-            object source,
-            AvaloniaProperty property)
-        {
-            if (notification.ErrorType == BindingErrorType.Error)
-            {
-                if (notification.Error is AggregateException aggregate)
-                {
-                    foreach (var inner in aggregate.InnerExceptions)
-                    {
-                        LogError(source, property, inner);
-                    }
-                }
-                else
-                {
-                    LogError(source, property, notification.Error);
-                }
-            }
-        }
-
-        private static void LogError(object source, AvaloniaProperty property, Exception e)
-        {
-            var level = LogEventLevel.Warning;
-
-            if (e is BindingChainException b &&
-                !string.IsNullOrEmpty(b.Expression) &&
-                string.IsNullOrEmpty(b.ExpressionErrorPoint))
-            {
-                // The error occurred at the root of the binding chain: it's possible that the
-                // DataContext isn't set up yet, so log at Information level instead of Warning
-                // to prevent spewing hundreds of errors.
-                level = LogEventLevel.Information;
-            }
-
-            Logger.Log(
-                level,
-                LogArea.Binding,
-                source,
-                "Error in binding to {Target}.{Property}: {Message}",
-                source,
-                property,
-                e.Message);
-        }
-    }
-}

+ 1 - 1
src/Avalonia.Base/PriorityValue.cs

@@ -197,7 +197,7 @@ namespace Avalonia
         /// <param name="error">The binding error.</param>
         public void LevelError(PriorityLevel level, BindingNotification error)
         {
-            error.LogIfError(Owner, Property);
+            Owner.LogError(Property, error.Error);
         }
 
         /// <summary>

+ 5 - 0
src/Avalonia.Base/ValueStore.cs

@@ -100,6 +100,11 @@ namespace Avalonia
             _owner.PriorityValueChanged(property, priority, oldValue, newValue);
         }
 
+        public void LogError(AvaloniaProperty property, Exception e)
+        {
+            _owner.LogBindingError(property, e);
+        }
+
         public IDictionary<AvaloniaProperty, object> GetSetValues() => _values;
 
         public object GetValue(AvaloniaProperty property)

+ 29 - 0
src/Avalonia.Visuals/Visual.cs

@@ -8,6 +8,7 @@ using System.Reactive.Linq;
 using Avalonia.Collections;
 using Avalonia.Data;
 using Avalonia.Logging;
+using Avalonia.LogicalTree;
 using Avalonia.Media;
 using Avalonia.Rendering;
 using Avalonia.VisualTree;
@@ -448,6 +449,34 @@ namespace Avalonia
             RaisePropertyChanged(VisualParentProperty, oldParent, newParent, BindingPriority.LocalValue);
         }
 
+        protected override sealed void LogBindingError(AvaloniaProperty property, Exception e)
+        {
+            // Don't log a binding error unless the control is attached to a logical or visual tree.
+            // In theory this should only need to check for logical tree attachment, but in practise
+            // due to ContentControlMixin only taking effect when the template has finished being
+            // applied, some controls are attached to the visual tree before the logical tree.
+            if (((ILogical)this).IsAttachedToLogicalTree || ((IVisual)this).IsAttachedToVisualTree)
+            {
+                if (e is BindingChainException b &&
+                    string.IsNullOrEmpty(b.ExpressionErrorPoint) &&
+                    DataContext == null)
+                {
+                    // The error occurred at the root of the binding chain and DataContext is null;
+                    // don't log this - the DataContext probably hasn't been set up yet.
+                    return;
+                }
+
+                Logger.Log(
+                    LogEventLevel.Warning,
+                    LogArea.Binding,
+                    this,
+                    "Error in binding to {Target}.{Property}: {Message}",
+                    this,
+                    property,
+                    e.Message);
+            }
+        }
+
         /// <summary>
         /// Gets the visual offset from the specified ancestor.
         /// </summary>