Browse Source

Got basic XAML control templates working.

Steven Kirk 10 years ago
parent
commit
dab821249e

+ 5 - 2
samples/XamlTestApplicationPcl/XamlTestApp.paml

@@ -11,8 +11,11 @@
       <Setter Property="Button.VerticalContentAlignment" Value="Center"/>
       <Setter Property="Button.Template">
         <ControlTemplate TargetType="Button">
-          <Border Padding="3">
-            <ContentPresenter Content="Content Here"/>
+          <Border Padding="3"
+                  Background="{TemplateBinding Background}"
+                  BorderBrush="{TemplateBinding BorderBrush}"
+                  BorderThickness="{TemplateBinding BorderThickness}">
+            <ContentPresenter Content="{TemplateBinding Content}"/>
           </Border>
         </ControlTemplate>
       </Setter>

+ 4 - 1
src/Markup/Perspex.Markup.Xaml/Binding/XamlBindingDefinition.cs

@@ -9,13 +9,16 @@ namespace Perspex.Markup.Xaml.Binding
     {
         public XamlBindingDefinition(
             string sourcePropertyPath, 
-            BindingMode bindingMode)
+            BindingMode bindingMode,
+            bool isTemplateBinding = false)
         {
             SourcePropertyPath = sourcePropertyPath;
             BindingMode = bindingMode;
+            IsTemplateBinding = isTemplateBinding;
         }
 
         public string SourcePropertyPath { get; }
         public BindingMode BindingMode { get; }
+        public bool IsTemplateBinding { get; }
     }
 }

+ 54 - 0
src/Markup/Perspex.Markup.Xaml/Binding/XamlTemplateBinding.cs

@@ -0,0 +1,54 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Reactive.Linq;
+using OmniXaml.TypeConversion;
+using Perspex.Controls;
+using Perspex.Styling;
+
+namespace Perspex.Markup.Xaml.Binding
+{
+    public class XamlTemplateBinding
+    {
+        private readonly ITypeConverterProvider _typeConverterProvider;
+
+        public XamlTemplateBinding()
+        {
+        }
+
+        public XamlTemplateBinding(ITypeConverterProvider typeConverterProvider)
+        {
+            _typeConverterProvider = typeConverterProvider;
+        }
+
+        public string SourcePropertyPath { get; set; }
+
+        public BindingMode BindingMode { get; set; }
+
+        public void Bind(PerspexObject instance, PerspexProperty targetProperty)
+        {
+            instance.GetObservable(Control.TemplatedParentProperty)
+                .Where(x => x != null)
+                .OfType<PerspexObject>()
+                .Take(1)
+                .Subscribe(x => BindToTemplatedParent(instance, targetProperty, x));
+        }
+
+        private void BindToTemplatedParent(
+            PerspexObject instance, 
+            PerspexProperty targetProperty,
+            PerspexObject templatedParent)
+        {
+            var sourceProperty = templatedParent.FindRegistered(SourcePropertyPath);
+
+            if (sourceProperty == null)
+            {
+                throw new InvalidOperationException(
+                    $"The property {SourcePropertyPath} could not be found on {instance.GetType()}.");
+            }
+
+            instance.Bind(targetProperty, templatedParent.GetObservable(sourceProperty), BindingPriority.Style);
+        }
+    }
+}

+ 18 - 5
src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs

@@ -109,13 +109,26 @@ namespace Perspex.Markup.Xaml.Context
                         $"Cannot find '{propertyName}' on '{instance.GetType()}");
                 }
 
-                var binding = new XamlBinding
+                if (!def.IsTemplateBinding)
                 {
-                    BindingMode = def.BindingMode,
-                    SourcePropertyPath = def.SourcePropertyPath,
-                };
+                    var binding = new XamlBinding
+                    {
+                        BindingMode = def.BindingMode,
+                        SourcePropertyPath = def.SourcePropertyPath,
+                    };
 
-                binding.Bind(perspexObject, property);
+                    binding.Bind(perspexObject, property);
+                }
+                else
+                {
+                    var binding = new XamlTemplateBinding
+                    {
+                        BindingMode = def.BindingMode,
+                        SourcePropertyPath = def.SourcePropertyPath,
+                    };
+
+                    binding.Bind(perspexObject, property);
+                }
             }
         }
 

+ 28 - 0
src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs

@@ -0,0 +1,28 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using OmniXaml;
+using Perspex.Markup.Xaml.Binding;
+
+namespace Perspex.Markup.Xaml.MarkupExtensions
+{
+    public class TemplateBindingExtension : MarkupExtension
+    {
+        public TemplateBindingExtension()
+        {
+        }
+
+        public TemplateBindingExtension(string path)
+        {
+            Path = path;
+        }
+
+        public override object ProvideValue(MarkupExtensionContext extensionContext)
+        {
+            return new XamlBindingDefinition(Path, Mode, true);
+        }
+
+        public string Path { get; set; }
+        public BindingMode Mode { get; set; }
+    }
+}

+ 2 - 0
src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj

@@ -40,6 +40,7 @@
     </Compile>
     <Compile Include="Binding\SourceBindingEndpoint.cs" />
     <Compile Include="Binding\TargetBindingEndpoint.cs" />
+    <Compile Include="Binding\XamlTemplateBinding.cs" />
     <Compile Include="Binding\XamlBinding.cs" />
     <Compile Include="Binding\XamlBindingDefinition.cs" />
     <Compile Include="Context\PerspexAttachableXamlMember.cs" />
@@ -55,6 +56,7 @@
     <Compile Include="Converters\SelectorTypeConverter.cs" />
     <Compile Include="Context\PerspexWiringContext.cs" />
     <Compile Include="Converters\TimeSpanTypeConverter.cs" />
+    <Compile Include="MarkupExtensions\TemplateBindingExtension.cs" />
     <Compile Include="MarkupExtensions\BindingExtension.cs" />
     <Compile Include="Converters\BrushTypeConverter.cs" />
     <Compile Include="Converters\BitmapTypeConverter.cs" />

+ 36 - 0
src/Perspex.Base/PerspexObjectExtensions.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 System.Linq;
 using System.Reactive.Linq;
 
 namespace Perspex
@@ -51,6 +52,41 @@ namespace Perspex
             return observable.Subscribe(e => SubscribeAdapter(e, handler));
         }
 
+        /// <summary>
+        /// Finds a registered property on a <see cref="PerspexObject"/> by name.
+        /// </summary>
+        /// <param name="o">The object.</param>
+        /// <param name="name">
+        /// The property name. If an attached property it should be in the form 
+        /// "OwnerType.PropertyName".
+        /// </param>
+        /// <returns>
+        /// The registered property or null if no matching property found.
+        /// </returns>
+        public static PerspexProperty FindRegistered(this PerspexObject o, string name)
+        {
+            Contract.Requires<ArgumentNullException>(o != null);
+            Contract.Requires<ArgumentNullException>(name != null);
+
+            var parts = name.Split('.');
+
+            if (parts.Length < 1 || parts.Length > 2)
+            {
+                throw new ArgumentException("Invalid property name.");
+            }
+
+            if (parts.Length == 1)
+            {
+                return o.GetRegisteredProperties()
+                    .FirstOrDefault(x => !x.IsAttached && x.Name == parts[0]);
+            }
+            else
+            {
+                return o.GetRegisteredProperties()
+                    .FirstOrDefault(x => x.IsAttached && x.OwnerType.Name == parts[0] && x.Name == parts[1]);
+            }
+        }
+
         /// <summary>
         /// Observer method for <see cref="AddClassHandler{TTarget}(IObservable{PerspexPropertyChangedEventArgs},
         /// Func{TTarget, Action{PerspexPropertyChangedEventArgs}})"/>.