Browse Source

Initial implementation of PerspexPropertyConverter.

pull/145/head
Steven Kirk 11 years ago
parent
commit
7e2a28c91f
  1. 10
      src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
  2. 57
      src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyConverter.cs
  3. 34
      src/Markup/Perspex.Markup.Xaml/Converters/SelectorConverter.cs
  4. 1
      src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
  5. 27
      src/Perspex.Base/PerspexObject.cs
  6. 5
      src/Perspex.Base/PerspexProperty.cs
  7. 28
      src/Perspex.Styling/Styling/Selector.cs
  8. 19
      tests/Perspex.Base.UnitTests/PerspexObjectTests_Metadata.cs
  9. 12
      tests/Perspex.Base.UnitTests/PerspexPropertyTests.cs

10
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
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
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
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" />

27
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
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
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;

19
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
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()
{

Loading…
Cancel
Save