From 20ca1bfa646dd34f9781118b3f470fd3ebc9312c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 29 Feb 2024 22:17:06 +0100 Subject: [PATCH] Make complied bindings use TypeConverters again. (#14767) * Update ncrunch config. * Add tests for converting strings to brushes. * Make complied bindings use TypeConverters. Certain conversions rely on type converters, which were disabled in compiled bindings since #13970 due to warnings that type converters are not trimming friendly. Ideally we'd be generating the type conversion logic in the XAML compiler, but in reality the problem with type converters and trimming is limited to type converters with generics, which is an edge case. For the moment re-enable the usage of type converters in compiled bindings until we implement generating the conversion code in the XAML compiler. --- ...lonia.FreeDesktop.net6.0.v3.ncrunchproject | 5 ++ ...eeDesktop.netstandard2.0.v3.ncrunchproject | 5 ++ ...nia.Themes.Simple.net6.0.v3.ncrunchproject | 3 ++ ...es.Simple.netstandard2.0.v3.ncrunchproject | 3 ++ .../Avalonia.X11.net6.0.v3.ncrunchproject | 5 ++ ...lonia.X11.netstandard2.0.v3.ncrunchproject | 5 ++ .../Data/Core/TargetTypeConverter.cs | 46 ++++++++++++++----- .../Data/BindingTests.cs | 24 ++++++++++ .../CompiledBindingExtensionTests.cs | 25 ++++++++++ 9 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 .ncrunch/Avalonia.FreeDesktop.net6.0.v3.ncrunchproject create mode 100644 .ncrunch/Avalonia.FreeDesktop.netstandard2.0.v3.ncrunchproject create mode 100644 .ncrunch/Avalonia.X11.net6.0.v3.ncrunchproject create mode 100644 .ncrunch/Avalonia.X11.netstandard2.0.v3.ncrunchproject diff --git a/.ncrunch/Avalonia.FreeDesktop.net6.0.v3.ncrunchproject b/.ncrunch/Avalonia.FreeDesktop.net6.0.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.FreeDesktop.net6.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.FreeDesktop.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.FreeDesktop.netstandard2.0.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.FreeDesktop.netstandard2.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject b/.ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject index 02eb0d211e..bc1af9a143 100644 --- a/.ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject @@ -1,5 +1,8 @@  + + ..\Avalonia.Themes.Fluent\Strings\InvariantResources.xaml + False \ No newline at end of file diff --git a/.ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject index 02eb0d211e..bc1af9a143 100644 --- a/.ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject @@ -1,5 +1,8 @@  + + ..\Avalonia.Themes.Fluent\Strings\InvariantResources.xaml + False \ No newline at end of file diff --git a/.ncrunch/Avalonia.X11.net6.0.v3.ncrunchproject b/.ncrunch/Avalonia.X11.net6.0.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.X11.net6.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.X11.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.X11.netstandard2.0.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.X11.netstandard2.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/src/Avalonia.Base/Data/Core/TargetTypeConverter.cs b/src/Avalonia.Base/Data/Core/TargetTypeConverter.cs index f633094860..7a4b0544bd 100644 --- a/src/Avalonia.Base/Data/Core/TargetTypeConverter.cs +++ b/src/Avalonia.Base/Data/Core/TargetTypeConverter.cs @@ -5,6 +5,7 @@ using Avalonia.Data.Converters; using System.Windows.Input; using Avalonia.Utilities; using static Avalonia.Utilities.TypeUtilities; +using System.ComponentModel; namespace Avalonia.Data.Core; @@ -65,6 +66,40 @@ internal abstract class TargetTypeConverter return true; } +#pragma warning disable IL2026 +#pragma warning disable IL2067 + // TODO: TypeConverters are not trimming friendly in some edge cases, we probably need + // to make compiled bindings emit conversion code at compile-time. + var toTypeConverter = TypeDescriptor.GetConverter(t); + var from = value.GetType(); + + if (toTypeConverter.CanConvertFrom(from)) + { + result = toTypeConverter.ConvertFrom(null, culture, value); + return true; + } + + var fromTypeConverter = TypeDescriptor.GetConverter(from); + + if (fromTypeConverter.CanConvertTo(t)) + { + result = fromTypeConverter.ConvertTo(null, culture, value, t); + return true; + } + + // TODO: This requires reflection: we probably need to make compiled bindings emit + // conversion code at compile-time. + if (FindTypeConversionOperatorMethod( + value.GetType(), + t, + OperatorType.Implicit | OperatorType.Explicit) is { } cast) + { + result = cast.Invoke(null, new[] { value }); + return true; + } +#pragma warning restore IL2067 +#pragma warning restore IL2026 + if (value is IConvertible convertible) { try @@ -79,17 +114,6 @@ internal abstract class TargetTypeConverter } } - // TODO: This requires reflection: we probably need to make compiled bindings emit - // conversion code at compile-time. - if (FindTypeConversionOperatorMethod( - value.GetType(), - t, - OperatorType.Implicit | OperatorType.Explicit) is { } cast) - { - result = cast.Invoke(null, new[] { value }); - return true; - } - result = null; return false; } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs index afc4a36fea..f724bf5f1c 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs @@ -6,6 +6,8 @@ using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Data; using Avalonia.Data.Converters; +using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.UnitTests; using Avalonia.VisualTree; using Xunit; @@ -94,6 +96,28 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data Assert.Equal("Foo,Bar", target.Text); } } + + [Fact] + public void Can_Bind_Brush_to_Hex_String() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var border = (Border)window.Content; + window.DataContext = new { HexString = "#ff0000" }; + + window.ApplyTemplate(); + + var brush = Assert.IsType(border.Background); + Assert.Equal(Colors.Red, brush.Color); + } + } } public class ConcatConverter : IMultiValueConverter diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 7c7b8bcb00..c2cec8cf79 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -24,6 +24,7 @@ using Avalonia.Markup.Xaml.MarkupExtensions; using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings; using Avalonia.Markup.Xaml.Templates; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Metadata; using Avalonia.UnitTests; using Xunit; @@ -2022,6 +2023,30 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void Can_Bind_Brush_To_Hex_String() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = $@" + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + var dataContext = new TestData { StringProperty = "#ff0000" }; + window.DataContext = dataContext; + + var brush = Assert.IsType(textBlock!.Background); + Assert.Equal(Colors.Red, brush.Color); + } + } + static void Throws(string type, Action cb) { try