diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
index 7ab5758f41..1a8ef84497 100644
--- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
+++ b/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"),
diff --git a/src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyConverter.cs b/src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyConverter.cs
new file mode 100644
index 0000000000..a835401ea9
--- /dev/null
+++ b/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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Markup/Perspex.Markup.Xaml/Converters/SelectorConverter.cs b/src/Markup/Perspex.Markup.Xaml/Converters/SelectorConverter.cs
new file mode 100644
index 0000000000..dfe858d065
--- /dev/null
+++ b/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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
index badd0b22a2..9308a92fae 100644
--- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
+++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
@@ -40,6 +40,7 @@
+
diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs
index 3763326577..2a0a34e350 100644
--- a/src/Perspex.Base/PerspexObject.cs
+++ b/src/Perspex.Base/PerspexObject.cs
@@ -71,6 +71,12 @@ namespace Perspex
private static readonly Dictionary> s_registered =
new Dictionary>();
+ ///
+ /// The registered attached properties by owner type.
+ ///
+ private static readonly Dictionary> s_attached =
+ new Dictionary>();
+
///
/// The parent object that inherited values are inherited from.
///
@@ -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
///
/// The type.
/// A collection of definitions.
- public static IEnumerable GetProperties(Type type)
+ public static IEnumerable GetRegisteredProperties(Type type)
{
Contract.Requires(type != null);
@@ -432,22 +438,7 @@ namespace Perspex
///
public IEnumerable GetRegisteredProperties()
{
- Type type = GetType();
-
- while (type != null)
- {
- List list;
-
- if (s_registered.TryGetValue(type, out list))
- {
- foreach (var p in list)
- {
- yield return p;
- }
- }
-
- type = type.GetTypeInfo().BaseType;
- }
+ return GetRegisteredProperties(GetType());
}
///
diff --git a/src/Perspex.Base/PerspexProperty.cs b/src/Perspex.Base/PerspexProperty.cs
index 20bacdb4e9..9950ec9028 100644
--- a/src/Perspex.Base/PerspexProperty.cs
+++ b/src/Perspex.Base/PerspexProperty.cs
@@ -68,6 +68,11 @@ namespace Perspex
Contract.Requires(valueType != null);
Contract.Requires(ownerType != null);
+ if (name.Contains("."))
+ {
+ throw new ArgumentException("'name' may not contain periods.");
+ }
+
Name = name;
PropertyType = valueType;
OwnerType = ownerType;
diff --git a/src/Perspex.Styling/Styling/Selector.cs b/src/Perspex.Styling/Styling/Selector.cs
index 1f9fb42784..7df3f77512 100644
--- a/src/Perspex.Styling/Styling/Selector.cs
+++ b/src/Perspex.Styling/Styling/Selector.cs
@@ -6,6 +6,34 @@ using System.Collections.Generic;
namespace Perspex.Styling
{
+ ///
+ /// A selector in a .
+ ///
+ ///
+ /// Selectors represented in markup using a CSS-like syntax, e.g. "Button < .dark" which
+ /// means "A child of a Button with the 'dark' class applied. The preceeding example would be
+ /// stored in 3 objects, linked by the property:
+ ///
+ /// -
+ /// .dark
+ ///
+ /// A selector that selects a control with the 'dark' class applied.
+ ///
+ ///
+ /// -
+ /// <
+ ///
+ /// A selector that selects a child of the previous selector.
+ ///
+ ///
+ /// -
+ /// Button
+ ///
+ /// A selector that selects a Button type.
+ ///
+ ///
+ ///
+ ///
public class Selector
{
private readonly Func _evaluate;
diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Metadata.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Metadata.cs
index 7b778414c0..f92fb12a66 100644
--- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Metadata.cs
+++ b/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 FredProperty =
PerspexProperty.Register("Fred");
}
+
+ private class AttachedOwner
+ {
+ public static readonly PerspexProperty AttachedProperty =
+ PerspexProperty.RegisterAttached("Attached");
+ }
}
}
diff --git a/tests/Perspex.Base.UnitTests/PerspexPropertyTests.cs b/tests/Perspex.Base.UnitTests/PerspexPropertyTests.cs
index d8f2840597..9f4fa62aa1 100644
--- a/tests/Perspex.Base.UnitTests/PerspexPropertyTests.cs
+++ b/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(() => new PerspexProperty(
+ "Foo.Bar",
+ typeof(Class1),
+ "Foo",
+ false,
+ BindingMode.OneWay,
+ null));
+ }
+
[Fact]
public void GetDefaultValue_Returns_Registered_Value()
{