浏览代码

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.
Steven Kirk 1 年之前
父节点
当前提交
20ca1bfa64

+ 5 - 0
.ncrunch/Avalonia.FreeDesktop.net6.0.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+  </Settings>
+</ProjectConfiguration>

+ 5 - 0
.ncrunch/Avalonia.FreeDesktop.netstandard2.0.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+  </Settings>
+</ProjectConfiguration>

+ 3 - 0
.ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject

@@ -1,5 +1,8 @@
 <ProjectConfiguration>
   <Settings>
+    <AdditionalFilesToIncludeForProject>
+      <Value>..\Avalonia.Themes.Fluent\Strings\InvariantResources.xaml</Value>
+    </AdditionalFilesToIncludeForProject>
     <InstrumentOutputAssembly>False</InstrumentOutputAssembly>
   </Settings>
 </ProjectConfiguration>

+ 3 - 0
.ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject

@@ -1,5 +1,8 @@
 <ProjectConfiguration>
   <Settings>
+    <AdditionalFilesToIncludeForProject>
+      <Value>..\Avalonia.Themes.Fluent\Strings\InvariantResources.xaml</Value>
+    </AdditionalFilesToIncludeForProject>
     <InstrumentOutputAssembly>False</InstrumentOutputAssembly>
   </Settings>
 </ProjectConfiguration>

+ 5 - 0
.ncrunch/Avalonia.X11.net6.0.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+  </Settings>
+</ProjectConfiguration>

+ 5 - 0
.ncrunch/Avalonia.X11.netstandard2.0.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+  </Settings>
+</ProjectConfiguration>

+ 35 - 11
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;
         }

+ 24 - 0
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 = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Data;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <Border Background='{Binding HexString}'/>
+</Window>";
+                var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+                var border = (Border)window.Content;
+                window.DataContext = new { HexString = "#ff0000" };
+
+                window.ApplyTemplate();
+
+                var brush = Assert.IsType<ImmutableSolidColorBrush>(border.Background);
+                Assert.Equal(Colors.Red, brush.Color);
+            }
+        }
     }
 
     public class ConcatConverter : IMultiValueConverter

+ 25 - 0
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 = $@"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
+        x:DataType='local:TestData'
+        x:CompileBindings='True'>
+    <TextBlock Name='textBlock' Background='{{Binding StringProperty}}'/>
+</Window>";
+                var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("textBlock");
+
+                var dataContext = new TestData { StringProperty = "#ff0000" };
+                window.DataContext = dataContext;
+
+                var brush = Assert.IsType<ImmutableSolidColorBrush>(textBlock!.Background);
+                Assert.Equal(Colors.Red, brush.Color);
+            }
+        }
+
         static void Throws(string type, Action cb)
         {
             try