diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs index 215a71e834..998e97686a 100644 --- a/src/Avalonia.Base/Data/Core/BindingExpression.cs +++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs @@ -8,6 +8,7 @@ using Avalonia.Data.Converters; using Avalonia.Data.Core.ExpressionNodes; using Avalonia.Data.Core.Parsers; using Avalonia.Logging; +using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot; namespace Avalonia.Data.Core; @@ -540,7 +541,7 @@ internal class BindingExpression : IObservable, } } - private static object? Convert( + private object? Convert( IValueConverter converter, object? converterParameter, object? value, @@ -554,8 +555,12 @@ internal class BindingExpression : IObservable, { var valueString = value?.ToString() ?? "(null)"; var valueTypeName = value?.GetType().FullName ?? "null"; - var ex = new InvalidCastException( - $"Could not convert '{valueString}' ({valueTypeName}) to {targetType} using '{converter}'.", e); + var message = $"Could not convert '{valueString}' ({valueTypeName}) to '{targetType}' using '{converter}'"; + + if (ShouldLogError(out var target)) + Log(target, $"{message}: {e.Message}", LogEventLevel.Warning); + + var ex = new InvalidCastException(message + '.', e); return new BindingNotification(ex, BindingErrorType.Error); } } diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Logging.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Logging.cs index d17667e222..0a00ffce81 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Logging.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Logging.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Globalization; using Avalonia.Controls; +using Avalonia.Controls.Converters; using Avalonia.Data; +using Avalonia.Data.Converters; using Avalonia.Data.Core; using Avalonia.Data.Core.Plugins.Reflection; using Avalonia.Input; @@ -248,6 +251,28 @@ namespace Avalonia.Markup.UnitTests.Data target.Bind(Control.MarginProperty, binding); } } + + [Fact] + public void Should_Log_Error_For_Unconvertible_Type_With_Converter() + { + var target = new Decorator { DataContext = new { Foo = new System.Version() } }; + var root = new TestRoot(target); + var binding = new Binding("Foo") + { + Converter = new ThrowingConverter(), + }; + + using (AssertLog( + target, + binding.Path, + "Could not convert '0.0' (System.Version) to 'Avalonia.Thickness' " + + "using 'Avalonia.Markup.UnitTests.Data.BindingTests_Logging+ThrowingConverter': " + + "The method or operation is not implemented.", + property: Control.MarginProperty)) + { + target.Bind(Control.MarginProperty, binding); + } + } } public class Fallback @@ -447,6 +472,19 @@ namespace Avalonia.Markup.UnitTests.Data public string? Foo { get; set; } } + private class ThrowingConverter : IValueConverter + { + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + private record LogMessage(LogEventLevel level, string area, object source, string messageTemplate, params object[] propertyValues); } }