diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
index 2f3f8c4daa..00479919a3 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
@@ -32,7 +32,7 @@
-
+
@@ -45,7 +45,7 @@
-
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs
similarity index 94%
rename from src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
rename to src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs
index a466714136..470cdbd08f 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs
@@ -12,13 +12,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
using Avalonia.Styling;
using System.ComponentModel;
- public class BindingExtension
+ public class ReflectionBindingExtension
{
- public BindingExtension()
+ public ReflectionBindingExtension()
{
}
- public BindingExtension(string path)
+ public ReflectionBindingExtension(string path)
{
Path = path;
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
index 2b09934057..b126834e5b 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
@@ -15,6 +15,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
private readonly XamlIlTransformerConfiguration _configuration;
private readonly IXamlIlType _contextType;
private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer;
+ private readonly AvaloniaBindingExtensionTransformer _bindingTransformer;
private AvaloniaXamlIlCompiler(AvaloniaXamlIlCompilerConfiguration configuration) : base(configuration, true)
{
@@ -32,7 +33,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Transformers.Insert(0, new XNameTransformer());
Transformers.Insert(1, new IgnoredDirectivesTransformer());
Transformers.Insert(2, _designTransformer = new AvaloniaXamlIlDesignPropertiesTransformer());
- Transformers.Insert(3, new AvaloniaBindingExtensionHackTransformer());
+ Transformers.Insert(3, _bindingTransformer = new AvaloniaBindingExtensionTransformer());
// Targeted
@@ -89,6 +90,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
set => _designTransformer.IsDesignMode = value;
}
+ public bool DefaultCompileBindings
+ {
+ get => _bindingTransformer.CompileBindingsByDefault;
+ set => _bindingTransformer.CompileBindingsByDefault = value;
+ }
+
public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlIlTypeBuilder tb, IXamlIlType overrideRootType)
{
var parsed = XDocumentXamlIlParser.Parse(xaml, new Dictionary
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionHackTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionHackTransformer.cs
deleted file mode 100644
index c89106312f..0000000000
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionHackTransformer.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using XamlIl.Ast;
-using XamlIl.Transform;
-
-namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
-{
- class AvaloniaBindingExtensionHackTransformer : IXamlIlAstTransformer
- {
- public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
- {
- // Our code base expects XAML parser to prefer `FooExtension` to `Foo` even with `` syntax
- // This is the legacy of Portable.Xaml, so we emulate that behavior here
-
- if (node is XamlIlAstXmlTypeReference tref
- && tref.Name == "Binding"
- && tref.XmlNamespace == "https://github.com/avaloniaui")
- tref.IsMarkupExtension = true;
- return node;
- }
- }
-}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs
new file mode 100644
index 0000000000..23904a19fb
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs
@@ -0,0 +1,73 @@
+using System.Linq;
+using XamlIl;
+using XamlIl.Ast;
+using XamlIl.Transform;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ class AvaloniaBindingExtensionTransformer : IXamlIlAstTransformer
+ {
+ public bool CompileBindingsByDefault { get; set; }
+
+ public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
+ {
+ if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlCompileBindingsNode)
+ {
+ return node;
+ }
+
+ if (node is XamlIlAstObjectNode obj)
+ {
+ foreach (var item in obj.Children)
+ {
+ if (item is XamlIlAstXmlDirective directive)
+ {
+ if (directive.Namespace == XamlNamespaces.Xaml2006
+ && directive.Name == "CompileBindings"
+ && directive.Values.Count == 1)
+ {
+ if (!(directive.Values[0] is XamlIlAstTextNode text
+ && bool.TryParse(text.Text, out var compileBindings)))
+ {
+ throw new XamlIlParseException("The value of x:CompileBindings must be a literal boolean value.", directive.Values[0]);
+ }
+
+ obj.Children.Remove(directive);
+
+ return new AvaloniaXamlIlCompileBindingsNode(obj, compileBindings);
+ }
+ }
+ }
+ }
+
+ // Our code base expects XAML parser to prefer `FooExtension` to `Foo` even with `` syntax
+ // This is the legacy of Portable.Xaml, so we emulate that behavior here
+
+ if (node is XamlIlAstXmlTypeReference tref
+ && tref.Name == "Binding"
+ && tref.XmlNamespace == "https://github.com/avaloniaui")
+ {
+ tref.IsMarkupExtension = true;
+
+ var compileBindings = context.ParentNodes()
+ .OfType()
+ .FirstOrDefault()
+ ?.CompileBindings ?? CompileBindingsByDefault;
+
+ tref.Name = compileBindings ? "CompiledBinding" : "ReflectionBinding";
+ }
+ return node;
+ }
+ }
+
+ internal class AvaloniaXamlIlCompileBindingsNode : XamlIlValueWithSideEffectNodeBase
+ {
+ public AvaloniaXamlIlCompileBindingsNode(IXamlIlAstValueNode value, bool compileBindings)
+ : base(value, value)
+ {
+ CompileBindings = compileBindings;
+ }
+
+ public bool CompileBindings { get; }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs
index b114b7476f..4a9e9fa3a7 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs
@@ -8,13 +8,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
{
- if (node is NestedScopeMetadataNode nestedScope)
- node = nestedScope.Value;
-
- if (node is AvaloniaXamlIlDataContextTypeMetadataNode dataContextType)
- node = dataContextType.Value;
-
- return node;
+ while (true)
+ {
+ if (node is NestedScopeMetadataNode nestedScope)
+ node = nestedScope.Value;
+ else if (node is AvaloniaXamlIlDataContextTypeMetadataNode dataContextType)
+ node = dataContextType.Value;
+ else if (node is AvaloniaXamlIlCompileBindingsNode compileBindings)
+ node = compileBindings.Value;
+ else
+ return node;
+ }
}
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
index b558019b33..6c7dbbb861 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
@@ -144,7 +144,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
private static AvaloniaXamlIlDataContextTypeMetadataNode ParseDataContext(XamlIlAstTransformationContext context, XamlIlAstObjectNode on, XamlIlAstObjectNode obj)
{
var bindingType = context.GetAvaloniaTypes().IBinding;
- if (!bindingType.IsAssignableFrom(obj.Type.GetClrType()) && !obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().BindingExtension))
+ if (!bindingType.IsAssignableFrom(obj.Type.GetClrType()) && !obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().ReflectionBindingExtension))
{
return new AvaloniaXamlIlDataContextTypeMetadataNode(on, obj.Type.GetClrType());
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
index 829be2cb1a..6f2861e2bf 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
@@ -37,7 +37,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlIlType CompiledBindingExtension { get; }
public IXamlIlType DataTemplate { get; }
public IXamlIlType IItemsPresenterHost { get; }
- public IXamlIlType BindingExtension { get; }
+ public IXamlIlType ReflectionBindingExtension { get; }
public IXamlIlType RelativeSource { get; }
@@ -92,7 +92,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
CompiledBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension");
DataTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.DataTemplate");
IItemsPresenterHost = cfg.TypeSystem.GetType("Avalonia.Controls.Presenters.IItemsPresenterHost");
- BindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.BindingExtension");
+ ReflectionBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension");
RelativeSource = cfg.TypeSystem.GetType("Avalonia.Data.RelativeSource");
}
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
index 43aad921ff..0062093f4c 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
@@ -519,6 +519,69 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
Assert.Equal("Test".Length.ToString(), target.Text);
}
}
+
+ [Fact]
+ public void CompilesBindingWhenRequested()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var textBlock = window.FindControl("textBlock");
+
+ var dataContext = new TestDataContext
+ {
+ StringProperty = "foobar"
+ };
+
+ window.DataContext = dataContext;
+
+ Assert.Equal(dataContext.StringProperty, textBlock.Text);
+ }
+ }
+
+ [Fact]
+ public void ThrowsOnInvalidBindingPathOnCompiledBindingEnabledViaDirective()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+";
+ var loader = new AvaloniaXamlLoader();
+ Assert.Throws(() => loader.Load(xaml));
+ }
+ }
+
+ [Fact]
+ public void ThrowsOnInvalidCompileBindingsDirective()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+";
+ var loader = new AvaloniaXamlLoader();
+ Assert.Throws(() => loader.Load(xaml));
+ }
+ }
}
public class TestDataContext