Browse Source

Fix TemplateBinding in custom IControlTemplate implementations (#17427)

* Add failing test for TemplateBinding inside custom control template

* Fix TemplateBinding XAML compilation error for custom IControlTemplate

---------

Co-authored-by: Max Katz <[email protected]>
Julien Lebosquain 1 year ago
parent
commit
8f7686fdfa

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs

@@ -11,7 +11,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
         public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
         public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
         {
         {
             if (!(node is XamlAstObjectNode on
             if (!(node is XamlAstObjectNode on
-                  && on.Type.GetClrType() == context.GetAvaloniaTypes().ControlTemplate))
+                  && context.GetAvaloniaTypes().IControlTemplate.IsAssignableFrom(on.Type.GetClrType())))
                 return node;
                 return node;
             var tt = on.Children.OfType<XamlAstXamlPropertyValueNode>().FirstOrDefault(ch =>
             var tt = on.Children.OfType<XamlAstXamlPropertyValueNode>().FirstOrDefault(ch =>
                                               ch.Property.GetClrProperty().Name == "TargetType");
                                               ch.Property.GetClrProperty().Name == "TargetType");

+ 2 - 0
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@@ -129,6 +129,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
         public IXamlType WindowTransparencyLevel { get; }
         public IXamlType WindowTransparencyLevel { get; }
         public IXamlType IReadOnlyListOfT { get; }
         public IXamlType IReadOnlyListOfT { get; }
         public IXamlType ControlTemplate { get; }
         public IXamlType ControlTemplate { get; }
+        public IXamlType IControlTemplate { get; }
         public IXamlType EventHandlerT {  get; }
         public IXamlType EventHandlerT {  get; }
         public IXamlMethod GetClassProperty { get; }
         public IXamlMethod GetClassProperty { get; }
 
 
@@ -325,6 +326,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             Style = cfg.TypeSystem.GetType("Avalonia.Styling.Style");
             Style = cfg.TypeSystem.GetType("Avalonia.Styling.Style");
             ControlTheme = cfg.TypeSystem.GetType("Avalonia.Styling.ControlTheme");
             ControlTheme = cfg.TypeSystem.GetType("Avalonia.Styling.ControlTheme");
             ControlTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.ControlTemplate");
             ControlTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.ControlTemplate");
+            IControlTemplate = cfg.TypeSystem.GetType("Avalonia.Controls.Templates.IControlTemplate");
             IReadOnlyListOfT = cfg.TypeSystem.GetType("System.Collections.Generic.IReadOnlyList`1");
             IReadOnlyListOfT = cfg.TypeSystem.GetType("System.Collections.Generic.IReadOnlyList`1");
             EventHandlerT = cfg.TypeSystem.GetType("System.EventHandler`1");
             EventHandlerT = cfg.TypeSystem.GetType("System.EventHandler`1");
             Interactivity = new InteractivityWellKnownTypes(cfg);
             Interactivity = new InteractivityWellKnownTypes(cfg);

+ 50 - 3
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs

@@ -6,10 +6,12 @@ using Avalonia.Controls;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
 using Avalonia.Data;
 using Avalonia.Data;
 using Avalonia.Diagnostics;
 using Avalonia.Diagnostics;
 using Avalonia.Markup.Xaml.Templates;
 using Avalonia.Markup.Xaml.Templates;
 using Avalonia.Media;
 using Avalonia.Media;
+using Avalonia.Metadata;
 using Avalonia.UnitTests;
 using Avalonia.UnitTests;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
 using Xunit;
 using Xunit;
@@ -437,13 +439,47 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
             Assert.Equal(RuntimeXamlDiagnosticSeverity.Info, warning.Severity);
             Assert.Equal(RuntimeXamlDiagnosticSeverity.Info, warning.Severity);
             Assert.Contains("'PART_MainContentBorder'", warning.Title);
             Assert.Contains("'PART_MainContentBorder'", warning.Title);
         }
         }
+
+#nullable enable
+
+        [Fact]
+        public void Custom_ControlTemplate_Allows_TemplateBindings()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var window = (Window)AvaloniaRuntimeXamlLoader.Load(
+                    """
+                    <Window xmlns="https://github.com/avaloniaui"
+                            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                            xmlns:controls="using:Avalonia.Markup.Xaml.UnitTests.Xaml">
+                        <Button Content="Foo">
+                            <Button.Template>
+                                <controls:CustomControlTemplate>
+                                    <ContentPresenter Name="PART_ContentPresenter"
+                                                      Content="{TemplateBinding Content}"/>
+                                </controls:CustomControlTemplate>
+                            </Button.Template>
+                        </Button>
+                    </Window>
+                    """);
+                var button = Assert.IsType<Button>(window.Content);
+
+                window.ApplyTemplate();
+                button.ApplyTemplate();
+
+                var presenter = button.Presenter;
+                Assert.NotNull(presenter);
+                Assert.Equal("Foo", presenter.Content);
+            }
+        }
     }
     }
+
     public class ListBoxHierarchyLine : Panel
     public class ListBoxHierarchyLine : Panel
     {
     {
-        public static readonly StyledProperty<DashStyle> LineDashStyleProperty =
-        AvaloniaProperty.Register<ListBoxHierarchyLine, DashStyle>(nameof(LineDashStyle));
+        public static readonly StyledProperty<DashStyle?> LineDashStyleProperty =
+            AvaloniaProperty.Register<ListBoxHierarchyLine, DashStyle?>(nameof(LineDashStyle));
 
 
-        public DashStyle LineDashStyle
+        public DashStyle? LineDashStyle
         {
         {
             get => GetValue(LineDashStyleProperty);
             get => GetValue(LineDashStyleProperty);
             set => SetValue(LineDashStyleProperty, value);
             set => SetValue(LineDashStyleProperty, value);
@@ -459,4 +495,15 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
     public class CustomButtonWithParts : CustomControlWithParts
     public class CustomButtonWithParts : CustomControlWithParts
     {
     {
     }
     }
+
+    public class CustomControlTemplate : IControlTemplate
+    {
+        [Content]
+        [TemplateContent]
+        public object? Content { get; set; }
+
+        public Type? TargetType { get; set; }
+
+        public TemplateResult<Control>? Build(TemplatedControl control) => TemplateContent.Load(Content);
+    }
 }
 }