diff --git a/Perspex.Base/PerspexObject.cs b/Perspex.Base/PerspexObject.cs index c12d986df8..cfe572c925 100644 --- a/Perspex.Base/PerspexObject.cs +++ b/Perspex.Base/PerspexObject.cs @@ -77,6 +77,23 @@ namespace Perspex private Dictionary values = new Dictionary(); + /// + /// Initializes a new instance of the class. + /// + public PerspexObject() + { + foreach (var p in this.GetAllValues()) + { + var e = new PerspexPropertyChangedEventArgs( + this, + p.Property, + PerspexProperty.UnsetValue, + p.CurrentValue); + + p.Property.NotifyInitialized(e); + } + } + /// /// Event handler for implementation. /// diff --git a/Perspex.Base/PerspexProperty.cs b/Perspex.Base/PerspexProperty.cs index da1526c4be..4b23e2599b 100644 --- a/Perspex.Base/PerspexProperty.cs +++ b/Perspex.Base/PerspexProperty.cs @@ -29,6 +29,11 @@ namespace Perspex /// private Dictionary defaultValues = new Dictionary(); + /// + /// Observable fired when this property changes on any . + /// + private Subject initialized = new Subject(); + /// /// Observable fired when this property changes on any . /// @@ -89,6 +94,21 @@ namespace Perspex /// public BindingMode DefaultBindingMode { get; private set; } + /// + /// Gets an observable that is fired when this property is initialized on a + /// new instance. + /// + /// + /// This observable is fired each time a new is constructed + /// for all properties registered on the object's type. The default value of the property + /// for the object is passed in the args' NewValue (OldValue will always be + /// . + /// + public IObservable Initialized + { + get { return this.initialized; } + } + /// /// Gets an observable that is fired when this property changes on any /// instance. @@ -270,6 +290,11 @@ namespace Perspex return this.Name; } + internal void NotifyInitialized(PerspexPropertyChangedEventArgs e) + { + this.initialized.OnNext(e); + } + internal void NotifyChanged(PerspexPropertyChangedEventArgs e) { this.changed.OnNext(e); diff --git a/Perspex.Controls/Control.cs b/Perspex.Controls/Control.cs index 2cf63cfe06..a19185d98d 100644 --- a/Perspex.Controls/Control.cs +++ b/Perspex.Controls/Control.cs @@ -33,7 +33,7 @@ namespace Perspex.Controls public static readonly PerspexProperty TemplatedParentProperty = PerspexProperty.Register("TemplatedParent", inherits: true); - private Classes classes; + private Classes classes = new Classes(); private DataTemplates dataTemplates; @@ -44,13 +44,8 @@ namespace Perspex.Controls static Control() { AffectsMeasure(IsVisibleProperty); - } - - public Control() - { - this.classes = new Classes(); - this.AddPseudoClass(IsPointerOverProperty, ":pointerover"); - this.AddPseudoClass(IsFocusedProperty, ":focus"); + PseudoClass(IsPointerOverProperty, ":pointerover"); + PseudoClass(IsFocusedProperty, ":focus"); } public Brush Background @@ -159,25 +154,44 @@ namespace Perspex.Controls internal set { this.SetValue(TemplatedParentProperty, value); } } - protected override void OnAttachedToVisualTree(IRenderRoot root) + protected static void PseudoClass(PerspexProperty property, string className) { - IStyler styler = Locator.Current.GetService(); - styler.ApplyStyles(this); + PseudoClass(property, x => x, className); } - protected void AddPseudoClass(PerspexProperty property, string className) + protected static void PseudoClass( + PerspexProperty property, + Func selector, + string className) { - this.GetObservable(property).Subscribe(x => + Contract.Requires(property != null); + Contract.Requires(selector != null); + Contract.Requires(className != null); + Contract.Requires(property != null); + + if (string.IsNullOrWhiteSpace(className)) { - if (x) - { - this.classes.Add(className); - } - else + throw new ArgumentException("Cannot supply an empty className."); + } + + Observable.Merge(property.Changed, property.Initialized) + .Subscribe(e => { - this.classes.Remove(className); - } - }); + if (selector((T)e.NewValue)) + { + ((Control)e.Sender).Classes.Add(className); + } + else + { + ((Control)e.Sender).Classes.Remove(className); + } + }); + } + + protected override void OnAttachedToVisualTree(IRenderRoot root) + { + IStyler styler = Locator.Current.GetService(); + styler.ApplyStyles(this); } } } diff --git a/Perspex.Controls/Primitives/ScrollBar.cs b/Perspex.Controls/Primitives/ScrollBar.cs index f8f4395cda..2cc44fccc1 100644 --- a/Perspex.Controls/Primitives/ScrollBar.cs +++ b/Perspex.Controls/Primitives/ScrollBar.cs @@ -26,21 +26,10 @@ namespace Perspex.Controls.Primitives public static readonly PerspexProperty OrientationProperty = PerspexProperty.Register("Orientation"); - public ScrollBar() + static ScrollBar() { - this.GetObservable(OrientationProperty).Subscribe(o => - { - if (o == Orientation.Horizontal) - { - this.Classes.Remove(":vertical"); - this.Classes.Add(":horizontal"); - } - else - { - this.Classes.Remove(":horizontal"); - this.Classes.Add(":vertical"); - } - }); + PseudoClass(OrientationProperty, x => x == Orientation.Horizontal, ":horizontal"); + PseudoClass(OrientationProperty, x => x == Orientation.Vertical, ":vertical"); } public double Minimum diff --git a/Perspex.Controls/Primitives/ToggleButton.cs b/Perspex.Controls/Primitives/ToggleButton.cs index d24840b45e..4c8a1c4463 100644 --- a/Perspex.Controls/Primitives/ToggleButton.cs +++ b/Perspex.Controls/Primitives/ToggleButton.cs @@ -13,10 +13,14 @@ namespace Perspex.Controls.Primitives public static readonly PerspexProperty IsCheckedProperty = PerspexProperty.Register("IsChecked"); + static ToggleButton() + { + PseudoClass(IsCheckedProperty, ":checked"); + } + public ToggleButton() { this.Click += (s, e) => this.IsChecked = !this.IsChecked; - this.AddPseudoClass(IsCheckedProperty, ":checked"); } public bool IsChecked diff --git a/Perspex.Controls/TabItem.cs b/Perspex.Controls/TabItem.cs index 570d8ba149..0506c9fdd3 100644 --- a/Perspex.Controls/TabItem.cs +++ b/Perspex.Controls/TabItem.cs @@ -13,10 +13,10 @@ namespace Perspex.Controls public static readonly PerspexProperty IsSelectedProperty = PerspexProperty.Register("IsSelected"); - public TabItem() + static TabItem() { - this.AddPseudoClass(IsSelectedProperty, ":selected"); AffectsRender(IsSelectedProperty); + PseudoClass(IsSelectedProperty, ":selected"); } public bool IsSelected diff --git a/Perspex.Controls/TreeViewItem.cs b/Perspex.Controls/TreeViewItem.cs index 6b6efada87..2a373e4663 100644 --- a/Perspex.Controls/TreeViewItem.cs +++ b/Perspex.Controls/TreeViewItem.cs @@ -21,9 +21,13 @@ namespace Perspex.Controls TreeView treeView; + static TreeViewItem() + { + PseudoClass(IsSelectedProperty, ":selected"); + } + public TreeViewItem() { - this.AddPseudoClass(IsSelectedProperty, ":selected"); AffectsRender(IsSelectedProperty); } diff --git a/Perspex.UnitTests/PerspexPropertyTests.cs b/Perspex.UnitTests/PerspexPropertyTests.cs index e824042e17..bfca46d648 100644 --- a/Perspex.UnitTests/PerspexPropertyTests.cs +++ b/Perspex.UnitTests/PerspexPropertyTests.cs @@ -83,22 +83,33 @@ namespace Perspex.UnitTests Assert.AreEqual("Bar", target.GetDefaultValue()); } + [TestMethod] + public void Initialized_Observable_Fired() + { + string value = null; + + Class1.FooProperty.Initialized.Subscribe(x => value = (string)x.NewValue); + var target = new Class1(); + + Assert.AreEqual("default", value); + } + [TestMethod] public void Changed_Observable_Fired() { var target = new Class1(); - bool fired = false; + string value = null; - Class1.FooProperty.Changed.Subscribe(x => fired = true); + Class1.FooProperty.Changed.Subscribe(x => value = (string)x.NewValue); target.SetValue(Class1.FooProperty, "newvalue"); - Assert.IsTrue(fired); + Assert.AreEqual("newvalue", value); } private class Class1 : PerspexObject { public static readonly PerspexProperty FooProperty = - PerspexProperty.Register("Foo"); + PerspexProperty.Register("Foo", "default"); } private class Class2 : Class1 diff --git a/Perspex.sln b/Perspex.sln index 87589151a6..47745d481e 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -76,7 +76,6 @@ Global {DABFD304-D6A4-4752-8123-C2CCF7AC7831}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DABFD304-D6A4-4752-8123-C2CCF7AC7831}.Debug|Any CPU.Build.0 = Debug|Any CPU {DABFD304-D6A4-4752-8123-C2CCF7AC7831}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DABFD304-D6A4-4752-8123-C2CCF7AC7831}.Release|Any CPU.Build.0 = Release|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Debug|Any CPU.Build.0 = Debug|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Release|Any CPU.ActiveCfg = Release|Any CPU