浏览代码

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>
 <ProjectConfiguration>
   <Settings>
   <Settings>
+    <AdditionalFilesToIncludeForProject>
+      <Value>..\Avalonia.Themes.Fluent\Strings\InvariantResources.xaml</Value>
+    </AdditionalFilesToIncludeForProject>
     <InstrumentOutputAssembly>False</InstrumentOutputAssembly>
     <InstrumentOutputAssembly>False</InstrumentOutputAssembly>
   </Settings>
   </Settings>
 </ProjectConfiguration>
 </ProjectConfiguration>

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

@@ -1,5 +1,8 @@
 <ProjectConfiguration>
 <ProjectConfiguration>
   <Settings>
   <Settings>
+    <AdditionalFilesToIncludeForProject>
+      <Value>..\Avalonia.Themes.Fluent\Strings\InvariantResources.xaml</Value>
+    </AdditionalFilesToIncludeForProject>
     <InstrumentOutputAssembly>False</InstrumentOutputAssembly>
     <InstrumentOutputAssembly>False</InstrumentOutputAssembly>
   </Settings>
   </Settings>
 </ProjectConfiguration>
 </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 System.Windows.Input;
 using Avalonia.Utilities;
 using Avalonia.Utilities;
 using static Avalonia.Utilities.TypeUtilities;
 using static Avalonia.Utilities.TypeUtilities;
+using System.ComponentModel;
 
 
 namespace Avalonia.Data.Core;
 namespace Avalonia.Data.Core;
 
 
@@ -65,6 +66,40 @@ internal abstract class TargetTypeConverter
                 return true;
                 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)
             if (value is IConvertible convertible)
             {
             {
                 try
                 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;
             result = null;
             return false;
             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.Controls.Presenters;
 using Avalonia.Data;
 using Avalonia.Data;
 using Avalonia.Data.Converters;
 using Avalonia.Data.Converters;
+using Avalonia.Media;
+using Avalonia.Media.Immutable;
 using Avalonia.UnitTests;
 using Avalonia.UnitTests;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
 using Xunit;
 using Xunit;
@@ -94,6 +96,28 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
                 Assert.Equal("Foo,Bar", target.Text);
                 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
     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.MarkupExtensions.CompiledBindings;
 using Avalonia.Markup.Xaml.Templates;
 using Avalonia.Markup.Xaml.Templates;
 using Avalonia.Media;
 using Avalonia.Media;
+using Avalonia.Media.Immutable;
 using Avalonia.Metadata;
 using Avalonia.Metadata;
 using Avalonia.UnitTests;
 using Avalonia.UnitTests;
 using Xunit;
 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)
         static void Throws(string type, Action cb)
         {
         {
             try
             try