diff --git a/Perspex.Base/PerspexProperty.cs b/Perspex.Base/PerspexProperty.cs index aa8ba55285..9d41a8d1e9 100644 --- a/Perspex.Base/PerspexProperty.cs +++ b/Perspex.Base/PerspexProperty.cs @@ -54,9 +54,10 @@ namespace Perspex Type valueType, Type ownerType, object defaultValue, - bool inherits, - BindingMode defaultBindingMode, - Func coerce) + bool inherits = false, + BindingMode defaultBindingMode = BindingMode.Default, + Func coerce = null, + bool isAttached = false) { Contract.Requires(name != null); Contract.Requires(valueType != null); @@ -69,38 +70,44 @@ namespace Perspex this.Inherits = inherits; this.DefaultBindingMode = defaultBindingMode; this.Coerce = coerce; + this.IsAttached = isAttached; } /// /// Gets the name of the property. /// - public string Name { get; private set; } + public string Name { get; } /// /// Gets the type of the property's value. /// - public Type PropertyType { get; private set; } + public Type PropertyType { get; } /// /// Gets the type of the class that registers the property. /// - public Type OwnerType { get; private set; } + public Type OwnerType { get; } /// /// Gets a value indicating whether the property inherits its value. /// - public bool Inherits { get; private set; } + public bool Inherits { get; } /// /// Gets the default binding mode for the property. /// /// - public BindingMode DefaultBindingMode { get; private set; } + public BindingMode DefaultBindingMode { get; } /// /// Gets the property's coerce function. /// - public Func Coerce { get; private set; } + public Func Coerce { get; } + + /// + /// Gets a value indicating whether this is an attached property. + /// + public bool IsAttached { get; } /// /// Gets an observable that is fired when this property is initialized on a @@ -153,7 +160,8 @@ namespace Perspex defaultValue, inherits, defaultBindingMode, - coerce); + coerce, + false); PerspexObject.Register(typeof(TOwner), result); @@ -182,12 +190,49 @@ namespace Perspex Contract.Requires(name != null); PerspexProperty result = new PerspexProperty( - typeof(TOwner) + "." + name, + name, typeof(TOwner), defaultValue, inherits, defaultBindingMode, - coerce); + coerce, + true); + + PerspexObject.Register(typeof(THost), result); + + return result; + } + + /// + /// Registers an attached . + /// + /// The type of the class that the property is to be registered on. + /// The type of the property's value. + /// The name of the property. + /// The type of the class that is registering the property. + /// The default value of the property. + /// Whether the property inherits its value. + /// The default binding mode for the property. + /// A coercion function. + /// A + public static PerspexProperty RegisterAttached( + string name, + Type ownerType, + TValue defaultValue = default(TValue), + bool inherits = false, + BindingMode defaultBindingMode = BindingMode.OneWay, + Func coerce = null) + { + Contract.Requires(name != null); + + PerspexProperty result = new PerspexProperty( + name, + ownerType, + defaultValue, + inherits, + defaultBindingMode, + coerce, + true); PerspexObject.Register(typeof(THost), result); @@ -335,13 +380,15 @@ namespace Perspex /// Whether the property inherits its value. /// The default binding mode for the property. /// A coercion function. + /// Whether the property is an attached property. public PerspexProperty( string name, Type ownerType, - TValue defaultValue, - bool inherits, - BindingMode defaultBindingMode, - Func coerce) + TValue defaultValue = default(TValue), + bool inherits = false, + BindingMode defaultBindingMode = BindingMode.Default, + Func coerce = null, + bool isAttached = false) : base( name, typeof(TValue), @@ -349,7 +396,8 @@ namespace Perspex defaultValue, inherits, defaultBindingMode, - Convert(coerce)) + Convert(coerce), + isAttached) { Contract.Requires(name != null); Contract.Requires(ownerType != null); diff --git a/Perspex.Diagnostics/Perspex.Diagnostics.csproj b/Perspex.Diagnostics/Perspex.Diagnostics.csproj index 97e263d315..5662f0d923 100644 --- a/Perspex.Diagnostics/Perspex.Diagnostics.csproj +++ b/Perspex.Diagnostics/Perspex.Diagnostics.csproj @@ -84,6 +84,7 @@ + diff --git a/Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs b/Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs index cd31c1655a..25d22bd364 100644 --- a/Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -18,8 +18,9 @@ namespace Perspex.Diagnostics.ViewModels if (control != null) { this.Properties = control.GetAllValues() - .Select(x => new PropertyDetails(x)) - .OrderBy(x => x.Name); + .Select(x => new PropertyDetails(x)) + .OrderBy(x => x.Name) + .OrderBy(x => x.IsAttached); } } diff --git a/Perspex.Diagnostics/ViewModels/PropertyDetails.cs b/Perspex.Diagnostics/ViewModels/PropertyDetails.cs index 6e71ced72a..1da18f0995 100644 --- a/Perspex.Diagnostics/ViewModels/PropertyDetails.cs +++ b/Perspex.Diagnostics/ViewModels/PropertyDetails.cs @@ -15,7 +15,11 @@ namespace Perspex.Diagnostics.ViewModels public PropertyDetails(PerspexPropertyValue value) { - this.Name = value.Property.Name; + this.Name = value.Property.IsAttached ? + string.Format("[{0}.{1}]", value.Property.OwnerType.Name, value.Property.Name) : + value.Property.Name; + this.IsAttached = value.Property.IsAttached; + this.value = value.CurrentValue ?? "(null)"; this.Priority = (value.PriorityValue != null) ? Enum.GetName(typeof(BindingPriority), value.PriorityValue.ValuePriority) : @@ -30,7 +34,11 @@ namespace Perspex.Diagnostics.ViewModels public string Name { get; - private set; + } + + public bool IsAttached + { + get; } public object Value diff --git a/Perspex.Diagnostics/Views/ControlDetailsView.cs b/Perspex.Diagnostics/Views/ControlDetailsView.cs index 3fb323026d..b3a05d4143 100644 --- a/Perspex.Diagnostics/Views/ControlDetailsView.cs +++ b/Perspex.Diagnostics/Views/ControlDetailsView.cs @@ -8,8 +8,11 @@ namespace Perspex.Diagnostics.Views { using Perspex.Controls; using Perspex.Diagnostics.ViewModels; + using Perspex.Styling; using ReactiveUI; using System; + using System.Collections; + using System.Collections.Generic; using System.Reactive.Linq; internal class ControlDetailsView : UserControl @@ -32,28 +35,49 @@ namespace Perspex.Diagnostics.Views private void InitializeComponent() { + Func> pt = this.PropertyTemplate; + this.Content = new ScrollViewer { - Content = new ItemsControl + Content = new Grid { - DataTemplates = new DataTemplates + ColumnDefinitions = new ColumnDefinitions + { + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(GridLength.Auto), + }, + Styles = new Styles { - new DataTemplate(x => - new StackPanel + new Style(x => x.Is()) + { + Setters = new[] { - Gap = 16, - Orientation = Orientation.Horizontal, - Children = new Controls - { - new TextBlock { Text = x.Name }, - new TextBlock { [!TextBlock.TextProperty] = x.WhenAnyValue(v => v.Value).Select(v => v?.ToString()) }, - new TextBlock { Text = x.Priority }, - }, - }), + new Setter(Control.MarginProperty, new Thickness(2)), + } + } }, - [!ItemsControl.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Properties), + [GridRepeater.TemplateProperty] = pt, + [!GridRepeater.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Properties), } }; } + + private IEnumerable PropertyTemplate(object i) + { + var property = (PropertyDetails)i; + + yield return new TextBlock + { + Text = property.Name + }; + + yield return new TextBlock + { + [!TextBlock.TextProperty] = property.WhenAnyValue(v => v.Value).Select(v => v?.ToString()), + }; + + yield return new TextBlock { Text = property.Priority }; + } } } diff --git a/Perspex.Diagnostics/Views/GridRepeater.cs b/Perspex.Diagnostics/Views/GridRepeater.cs new file mode 100644 index 0000000000..7d494f0527 --- /dev/null +++ b/Perspex.Diagnostics/Views/GridRepeater.cs @@ -0,0 +1,72 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Diagnostics.Views +{ + using Perspex.Controls; + using Perspex.Controls.Templates; + using System; + using System.Collections; + using System.Collections.Generic; + + internal static class GridRepeater + { + public static readonly PerspexProperty ItemsProperty = + PerspexProperty.RegisterAttached("Items", typeof(GridRepeater)); + + public static readonly PerspexProperty>> TemplateProperty = + PerspexProperty.RegisterAttached>>("Template", typeof(GridRepeater)); + + static GridRepeater() + { + ItemsProperty.Changed.Subscribe(ItemsChanged); + } + + private static void ItemsChanged(PerspexPropertyChangedEventArgs e) + { + var grid = (Grid)e.Sender; + var items = (IEnumerable)e.NewValue; + var template = grid.GetValue(TemplateProperty); + + grid.Children.Clear(); + + if (items != null) + { + int count = 0; + int cols = grid.ColumnDefinitions.Count; + + foreach (var item in items) + { + foreach (var control in template(item)) + { + grid.Children.Add(control); + Grid.SetColumn(control, count % cols); + Grid.SetRow(control, count / cols); + ++count; + } + } + + int rows = (int)Math.Ceiling((double)count / cols); + int difference = rows - grid.RowDefinitions.Count; + + if (difference > 0) + { + for (int i = 0; i < difference; ++i) + { + grid.RowDefinitions.Add(new RowDefinition(GridLength.Auto)); + } + } + else if (difference < 0) + { + for (int i = 0; i < difference; ++i) + { + grid.RowDefinitions.RemoveAt(grid.RowDefinitions.Count - 1); + } + } + } + } + } +}