Browse Source

Added <Template>

The new <Template> is a template that can be used as a setter value,
meaning that the setter will materialize the template each time it sets
the value. Make assigning a control to `Setter.Value` throw an error
indicating that the control should be wrapped in a template.
Steven Kirk 9 years ago
parent
commit
d772017768

+ 3 - 0
src/Avalonia.Controls/Templates/FuncTemplate`1.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using Avalonia.Styling;
 
 namespace Avalonia.Controls.Templates
 {
@@ -34,5 +35,7 @@ namespace Avalonia.Controls.Templates
         {
             return _func();
         }
+
+        object ITemplate.Build() => Build();
     }
 }

+ 4 - 2
src/Avalonia.Controls/Templates/ITemplate`1.cs

@@ -1,13 +1,15 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using Avalonia.Styling;
+
 namespace Avalonia.Controls
 {
     /// <summary>
     /// Creates a control.
     /// </summary>
     /// <typeparam name="TControl">The type of control.</typeparam>
-    public interface ITemplate<TControl> where TControl : IControl
+    public interface ITemplate<TControl> : ITemplate where TControl : IControl
     {
         /// <summary>
         /// Creates the control.
@@ -15,6 +17,6 @@ namespace Avalonia.Controls
         /// <returns>
         /// The created control.
         /// </returns>
-        TControl Build();
+        new TControl Build();
     }
 }

+ 2 - 1
src/Avalonia.Styling/Avalonia.Styling.csproj

@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
   <PropertyGroup>
@@ -49,6 +49,7 @@
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Styling\ActivatedSubject.cs" />
     <Compile Include="Styling\ActivatedValue.cs" />
+    <Compile Include="Styling\ITemplate.cs" />
     <Compile Include="Styling\TemplateSelector.cs" />
     <Compile Include="Styling\DescendentSelector.cs" />
     <Compile Include="Styling\ChildSelector.cs" />

+ 10 - 0
src/Avalonia.Styling/Styling/ITemplate.cs

@@ -0,0 +1,10 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Avalonia.Styling
+{
+    public interface ITemplate
+    {
+        object Build();
+    }
+}

+ 29 - 5
src/Avalonia.Styling/Styling/Setter.cs

@@ -19,6 +19,8 @@ namespace Avalonia.Styling
     /// </remarks>
     public class Setter : ISetter
     {
+        private object _value;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="Setter"/> class.
         /// </summary>
@@ -54,8 +56,22 @@ namespace Avalonia.Styling
         [DependsOn(nameof(Property))]
         public object Value
         {
-            get;
-            set;
+            get
+            {
+                return _value;
+            }
+
+            set
+            {
+                if (value is IStyleable)
+                {
+                    throw new ArgumentException(
+                        "Cannot assign a control to Style.Value. Wrap the control in a <Template>.",
+                        "value");
+                }
+
+                _value = value;
+            }
         }
 
         /// <summary>
@@ -75,17 +91,25 @@ namespace Avalonia.Styling
                 throw new InvalidOperationException("Setter.Property must be set.");
             }
 
-            var binding = Value as IBinding;
+            var value = Value;
+            var binding = value as IBinding;
 
             if (binding == null)
             {
+                var template = value as ITemplate;
+
+                if (template != null)
+                {
+                    value = template.Build();
+                }
+
                 if (activator == null)
                 {
-                    return control.Bind(Property, ObservableEx.SingleValue(Value), BindingPriority.Style);
+                    return control.Bind(Property, ObservableEx.SingleValue(value), BindingPriority.Style);
                 }
                 else
                 {
-                    var activated = new ActivatedValue(activator, Value, description);
+                    var activated = new ActivatedValue(activator, value, description);
                     return control.Bind(Property, activated, BindingPriority.StyleTrigger);
                 }
             }

+ 2 - 8
src/Markup/Avalonia.Markup.Xaml/Templates/FocusAdornerTemplate.cs

@@ -3,17 +3,11 @@
 
 using Avalonia.Controls;
 using Avalonia.Metadata;
+using Avalonia.Styling;
 
 namespace Avalonia.Markup.Xaml.Templates
 {
-    public class FocusAdornerTemplate : ITemplate<IControl>
+    public class FocusAdornerTemplate : Template
     {
-        [Content]
-        public TemplateContent Content { get; set; }
-
-        public IControl Build()
-        {
-            return Content.Load();
-        }
     }
 }

+ 5 - 5
src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs

@@ -1,11 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using Avalonia.Controls;
 using Avalonia.Metadata;
+using Avalonia.Styling;
 
 namespace Avalonia.Markup.Xaml.Templates
 {
@@ -18,5 +16,7 @@ namespace Avalonia.Markup.Xaml.Templates
         {
             return (IPanel)Content.Load();
         }
+
+        object ITemplate.Build() => Build();
     }
 }

+ 10 - 1
src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs

@@ -1,10 +1,19 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using Avalonia.Controls;
+using Avalonia.Metadata;
+using Avalonia.Styling;
+
 namespace Avalonia.Markup.Xaml.Templates
 {
-    public class Template
+    public class Template : ITemplate<IControl>
     {
+        [Content]
         public TemplateContent Content { get; set; }
+
+        public IControl Build() => Content.Load();
+
+        object ITemplate.Build() => Build();
     }
 }

+ 23 - 0
tests/Avalonia.Styling.UnitTests/SetterTests.cs

@@ -6,11 +6,21 @@ using Moq;
 using Avalonia.Controls;
 using Avalonia.Data;
 using Xunit;
+using System;
+using Avalonia.Controls.Templates;
 
 namespace Avalonia.Styling.UnitTests
 {
     public class SetterTests
     {
+        [Fact]
+        public void Cannot_Assign_Control_To_Value()
+        {
+            var target = new Setter();
+
+            Assert.Throws<ArgumentException>(() => target.Value = new Border());
+        }
+
         [Fact]
         public void Setter_Should_Apply_Binding_To_Property()
         {
@@ -25,5 +35,18 @@ namespace Avalonia.Styling.UnitTests
 
             Assert.Equal("foo", control.Text);
         }
+
+        [Fact]
+        public void Setter_Should_Materialize_Template_To_Property()
+        {
+            var control = new Decorator();
+            var template = new FuncTemplate<Canvas>(() => new Canvas());
+            var style = Mock.Of<IStyle>();
+            var setter = new Setter(Decorator.ChildProperty, template);
+
+            setter.Apply(style, control, null);
+
+            Assert.IsType<Canvas>(control.Child);
+        }
     }
 }