Browse Source

Initial implementation of PerspexPropertyConverter.

Steven Kirk 10 years ago
parent
commit
7e2a28c91f

+ 10 - 0
src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs

@@ -16,6 +16,7 @@ using Perspex.Markup.Xaml.DataBinding;
 using Perspex.Markup.Xaml.MarkupExtensions;
 using Perspex.Media;
 using Perspex.Media.Imaging;
+using Perspex.Styling;
 
 namespace Perspex.Markup.Xaml.Context
 {
@@ -42,6 +43,7 @@ namespace Perspex.Markup.Xaml.Context
             var rootType = typeof(Control);
             var bindingType = typeof(BindingExtension);
             var templateType = typeof(XamlDataTemplate);
+            var styleType = typeof(Style);
 
             var definitionForRoot = XamlNamespace
                 .Map(PerspexNs)
@@ -59,6 +61,11 @@ namespace Perspex.Markup.Xaml.Context
                                 bindingType.Namespace,
                             }),
                         Route.Assembly(templateType.GetTypeInfo().Assembly).WithNamespaces(
+                            new[]
+                            {
+                                templateType.Namespace,
+                            }),
+                        Route.Assembly(styleType.GetTypeInfo().Assembly).WithNamespaces(
                             new[]
                             {
                                 templateType.Namespace,
@@ -84,7 +91,9 @@ namespace Perspex.Markup.Xaml.Context
                 new TypeConverterRegistration(typeof(Brush), new BrushConverter()),
                 new TypeConverterRegistration(typeof(ColumnDefinitions), new ColumnDefinitionsTypeConverter()),
                 new TypeConverterRegistration(typeof(GridLength), new GridLengthTypeConverter()),
+                new TypeConverterRegistration(typeof(PerspexProperty), new PerspexPropertyConverter()),
                 new TypeConverterRegistration(typeof(RowDefinitions), new RowDefinitionsTypeConverter()),
+                new TypeConverterRegistration(typeof(Selector), new SelectorConverter()),
                 new TypeConverterRegistration(typeof(Thickness), new ThicknessConverter()),
             };
 
@@ -101,6 +110,7 @@ namespace Perspex.Markup.Xaml.Context
                 new ContentPropertyDefinition(typeof(Decorator), "Child"),
                 new ContentPropertyDefinition(typeof(ItemsControl), "Items"),
                 new ContentPropertyDefinition(typeof(Panel), "Children"),
+                new ContentPropertyDefinition(typeof(Style), "Setters"),
                 new ContentPropertyDefinition(typeof(TextBlock), "Text"),
                 new ContentPropertyDefinition(typeof(TextBox), "Text"),
                 new ContentPropertyDefinition(typeof(XamlDataTemplate), "Content"),

+ 57 - 0
src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyConverter.cs

@@ -0,0 +1,57 @@
+// 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.Globalization;
+using System.Linq;
+using OmniXaml.ObjectAssembler;
+using OmniXaml.TypeConversion;
+using Perspex.Markup.Xaml.Parsers;
+
+namespace Perspex.Markup.Xaml.Converters
+{
+    public class PerspexPropertyConverter : ITypeConverter
+    {
+        public bool CanConvertFrom(IXamlTypeConverterContext context, Type sourceType)
+        {
+            return sourceType == typeof(string);
+        }
+
+        public bool CanConvertTo(IXamlTypeConverterContext context, Type destinationType)
+        {
+            return false;
+        }
+
+        public object ConvertFrom(IXamlTypeConverterContext context, CultureInfo culture, object value)
+        {
+            var s = (string)value;
+            var lastDot = s.LastIndexOf('.');
+
+            if (lastDot == -1)
+            {
+                throw new NotSupportedException("PerspexProperties must currently be fully qualified.");
+            }
+
+            var typeName = s.Substring(0, lastDot);
+            var propertyName = s.Substring(lastDot + 1);
+
+            // TODO: Doesn't handle xml namespaces - use GetByQualifiedName when it works with the
+            // default namespace.            
+            var type = context.TypeRepository.GetByPrefix("", typeName)?.UnderlyingType;
+
+            if (type == null)
+            {
+                throw new InvalidOperationException($"Could not find type '{typeName}'.");
+            }
+
+            // TODO: Handle attached properties.
+            // TODO: Give decent error message for not found property.
+            return PerspexObject.GetRegisteredProperties(type).Single(x => x.Name == propertyName);
+        }
+
+        public object ConvertTo(IXamlTypeConverterContext context, CultureInfo culture, object value, Type destinationType)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 34 - 0
src/Markup/Perspex.Markup.Xaml/Converters/SelectorConverter.cs

@@ -0,0 +1,34 @@
+// 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.Globalization;
+using OmniXaml.TypeConversion;
+using Perspex.Markup.Xaml.Parsers;
+
+namespace Perspex.Markup.Xaml.Converters
+{
+    public class SelectorConverter : ITypeConverter
+    {
+        public bool CanConvertFrom(IXamlTypeConverterContext context, Type sourceType)
+        {
+            return sourceType == typeof(string);
+        }
+
+        public bool CanConvertTo(IXamlTypeConverterContext context, Type destinationType)
+        {
+            return false;
+        }
+
+        public object ConvertFrom(IXamlTypeConverterContext context, CultureInfo culture, object value)
+        {
+            var parser = new SelectorParser((t, ns) => context.TypeRepository.GetByPrefix(ns ?? "", t).UnderlyingType);
+            return parser.Parse((string)value);
+        }
+
+        public object ConvertTo(IXamlTypeConverterContext context, CultureInfo culture, object value, Type destinationType)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

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

@@ -40,6 +40,7 @@
     </Compile>
     <Compile Include="Converters\RowDefinitionsTypeConverter.cs" />
     <Compile Include="Converters\ColumnDefinitionsTypeConverter.cs" />
+    <Compile Include="Converters\PerspexPropertyConverter.cs" />
     <Compile Include="Converters\SelectorConverter.cs" />
     <Compile Include="Converters\ThicknessConverter.cs" />
     <Compile Include="Context\PerspexWiringContext.cs" />

+ 9 - 18
src/Perspex.Base/PerspexObject.cs

@@ -71,6 +71,12 @@ namespace Perspex
         private static readonly Dictionary<Type, List<PerspexProperty>> s_registered =
             new Dictionary<Type, List<PerspexProperty>>();
 
+        /// <summary>
+        /// The registered attached properties by owner type.
+        /// </summary>
+        private static readonly Dictionary<Type, List<PerspexProperty>> s_attached =
+            new Dictionary<Type, List<PerspexProperty>>();
+
         /// <summary>
         /// The parent object that inherited values are inherited from.
         /// </summary>
@@ -154,7 +160,7 @@ namespace Perspex
                         _inheritanceParent.PropertyChanged -= ParentPropertyChanged;
                     }
 
-                    var inherited = (from property in GetProperties(GetType())
+                    var inherited = (from property in GetRegisteredProperties(GetType())
                                      where property.Inherits
                                      select new
                                      {
@@ -245,7 +251,7 @@ namespace Perspex
         /// </summary>
         /// <param name="type">The type.</param>
         /// <returns>A collection of <see cref="PerspexProperty"/> definitions.</returns>
-        public static IEnumerable<PerspexProperty> GetProperties(Type type)
+        public static IEnumerable<PerspexProperty> GetRegisteredProperties(Type type)
         {
             Contract.Requires<NullReferenceException>(type != null);
 
@@ -432,22 +438,7 @@ namespace Perspex
         /// </returns>
         public IEnumerable<PerspexProperty> GetRegisteredProperties()
         {
-            Type type = GetType();
-
-            while (type != null)
-            {
-                List<PerspexProperty> list;
-
-                if (s_registered.TryGetValue(type, out list))
-                {
-                    foreach (var p in list)
-                    {
-                        yield return p;
-                    }
-                }
-
-                type = type.GetTypeInfo().BaseType;
-            }
+            return GetRegisteredProperties(GetType());
         }
 
         /// <summary>

+ 5 - 0
src/Perspex.Base/PerspexProperty.cs

@@ -68,6 +68,11 @@ namespace Perspex
             Contract.Requires<NullReferenceException>(valueType != null);
             Contract.Requires<NullReferenceException>(ownerType != null);
 
+            if (name.Contains("."))
+            {
+                throw new ArgumentException("'name' may not contain periods.");
+            }
+
             Name = name;
             PropertyType = valueType;
             OwnerType = ownerType;

+ 28 - 0
src/Perspex.Styling/Styling/Selector.cs

@@ -6,6 +6,34 @@ using System.Collections.Generic;
 
 namespace Perspex.Styling
 {
+    /// <summary>
+    /// A selector in a <see cref="Style"/>.
+    /// </summary>
+    /// <remarks>
+    /// Selectors represented in markup using a CSS-like syntax, e.g. "Button &lt; .dark" which 
+    /// means "A child of a Button with the 'dark' class applied. The preceeding example would be
+    /// stored in 3 <see cref="Selector"/> objects, linked by the <see cref="Previous"/> property:
+    /// <list type="number">
+    /// <item>
+    ///   <term>.dark</term>
+    ///   <description>
+    ///     A selector that selects a control with the 'dark' class applied.
+    ///   </description>
+    /// </item>
+    /// <item>
+    ///   <term>&lt;</term>
+    ///   <description>
+    ///     A selector that selects a child of the previous selector.
+    ///   </description>
+    /// </item>
+    /// <item>
+    ///   <term>Button</term>
+    ///   <description>
+    ///     A selector that selects a Button type.
+    ///   </description>
+    /// </item>
+    /// </list>
+    /// </remarks>
     public class Selector
     {
         private readonly Func<IStyleable, SelectorMatch> _evaluate;

+ 13 - 6
tests/Perspex.Base.UnitTests/PerspexObjectTests_Metadata.cs

@@ -15,22 +15,23 @@ namespace Perspex.Base.UnitTests
             PerspexProperty p;
             p = Class1.FooProperty;
             p = Class2.BarProperty;
+            p = AttachedOwner.AttachedProperty;
         }
 
         [Fact]
-        public void GetProperties_Returns_Registered_Properties()
+        public void GetRegisteredProperties_Returns_Registered_Properties()
         {
-            string[] names = PerspexObject.GetProperties(typeof(Class1)).Select(x => x.Name).ToArray();
+            string[] names = PerspexObject.GetRegisteredProperties(typeof(Class1)).Select(x => x.Name).ToArray();
 
-            Assert.Equal(new[] { "Foo", "Baz", "Qux" }, names);
+            Assert.Equal(new[] { "Foo", "Baz", "Qux", "Attached" }, names);
         }
 
         [Fact]
-        public void GetProperties_Returns_Registered_Properties_For_Base_Types()
+        public void GetRegisteredProperties_Returns_Registered_Properties_For_Base_Types()
         {
-            string[] names = PerspexObject.GetProperties(typeof(Class2)).Select(x => x.Name).ToArray();
+            string[] names = PerspexObject.GetRegisteredProperties(typeof(Class2)).Select(x => x.Name).ToArray();
 
-            Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux" }, names);
+            Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux", "Attached" }, names);
         }
 
         private class Class1 : PerspexObject
@@ -56,5 +57,11 @@ namespace Perspex.Base.UnitTests
             public static readonly PerspexProperty<double?> FredProperty =
                 PerspexProperty.Register<Class2, double?>("Fred");
         }
+
+        private class AttachedOwner
+        {
+            public static readonly PerspexProperty<string> AttachedProperty =
+                PerspexProperty.RegisterAttached<AttachedOwner, Class1, string>("Attached");
+        }
     }
 }

+ 12 - 0
tests/Perspex.Base.UnitTests/PerspexPropertyTests.cs

@@ -25,6 +25,18 @@ namespace Perspex.Base.UnitTests
             Assert.Equal(false, target.Inherits);
         }
 
+        [Fact]
+        public void Name_Cannot_Contain_Periods()
+        {
+            Assert.Throws<ArgumentException>(() => new PerspexProperty<string>(
+                "Foo.Bar",
+                typeof(Class1),
+                "Foo",
+                false,
+                BindingMode.OneWay,
+                null));
+        }
+
         [Fact]
         public void GetDefaultValue_Returns_Registered_Value()
         {