Переглянути джерело

Merge pull request #3292 from AvaloniaUI/refactor/remove-property-initialized

Remove AvaloniaProperty.Initialized and PseudoClass static methods
Steven Kirk 5 роки тому
батько
коміт
15140b83b3

+ 0 - 1
src/Avalonia.Base/AvaloniaObject.cs

@@ -34,7 +34,6 @@ namespace Avalonia
         public AvaloniaObject()
         {
             VerifyAccess();
-            AvaloniaPropertyRegistry.Instance.NotifyInitialized(this);
         }
 
         /// <summary>

+ 0 - 39
src/Avalonia.Base/AvaloniaProperty.cs

@@ -20,7 +20,6 @@ namespace Avalonia
         public static readonly object UnsetValue = new UnsetValueType();
 
         private static int s_nextId;
-        private readonly Subject<AvaloniaPropertyChangedEventArgs> _initialized;
         private readonly Subject<AvaloniaPropertyChangedEventArgs> _changed;
         private readonly PropertyMetadata _defaultMetadata;
         private readonly Dictionary<Type, PropertyMetadata> _metadata;
@@ -53,7 +52,6 @@ namespace Avalonia
                 throw new ArgumentException("'name' may not contain periods.");
             }
 
-            _initialized = new Subject<AvaloniaPropertyChangedEventArgs>();
             _changed = new Subject<AvaloniaPropertyChangedEventArgs>();
             _metadata = new Dictionary<Type, PropertyMetadata>();
 
@@ -81,7 +79,6 @@ namespace Avalonia
             Contract.Requires<ArgumentNullException>(source != null);
             Contract.Requires<ArgumentNullException>(ownerType != null);
 
-            _initialized = source._initialized;
             _changed = source._changed;
             _metadata = new Dictionary<Type, PropertyMetadata>();
 
@@ -136,22 +133,6 @@ namespace Avalonia
         /// </summary>
         public virtual bool IsReadOnly => false;
 
-        /// <summary>
-        /// Gets an observable that is fired when this property is initialized on a
-        /// new <see cref="AvaloniaObject"/> instance.
-        /// </summary>
-        /// <remarks>
-        /// This observable is fired each time a new <see cref="AvaloniaObject"/> 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
-        /// <see cref="UnsetValue"/>.
-        /// </remarks>
-        /// <value>
-        /// An observable that is fired when this property is initialized on a new
-        /// <see cref="AvaloniaObject"/> instance.
-        /// </value>
-        public IObservable<AvaloniaPropertyChangedEventArgs> Initialized => _initialized;
-
         /// <summary>
         /// Gets an observable that is fired when this property changes on any
         /// <see cref="AvaloniaObject"/> instance.
@@ -488,26 +469,6 @@ namespace Avalonia
             return Name;
         }
 
-        /// <summary>
-        /// True if <see cref="Initialized"/> has any observers.
-        /// </summary>
-        internal bool HasNotifyInitializedObservers => _initialized.HasObservers;
-
-        /// <summary>
-        /// Notifies the <see cref="Initialized"/> observable.
-        /// </summary>
-        /// <param name="o">The object being initialized.</param>
-        internal abstract void NotifyInitialized(IAvaloniaObject o);
-
-        /// <summary>
-        /// Notifies the <see cref="Initialized"/> observable.
-        /// </summary>
-        /// <param name="e">The observable arguments.</param>
-        internal void NotifyInitialized(AvaloniaPropertyChangedEventArgs e)
-        {
-            _initialized.OnNext(e);
-        }
-
         /// <summary>
         /// Notifies the <see cref="Changed"/> observable.
         /// </summary>

+ 0 - 45
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@@ -415,51 +415,6 @@ namespace Avalonia
             _inheritedCache.Clear();
         }
 
-        internal void NotifyInitialized(AvaloniaObject o)
-        {
-            Contract.Requires<ArgumentNullException>(o != null);
-
-            var type = o.GetType();
-
-            if (!_initializedCache.TryGetValue(type, out var initializationData))
-            {
-                var visited = new HashSet<AvaloniaProperty>();
-
-                initializationData = new List<PropertyInitializationData>();
-
-                foreach (AvaloniaProperty property in GetRegistered(type))
-                {
-                    if (property.IsDirect)
-                    {
-                        initializationData.Add(new PropertyInitializationData(property, (IDirectPropertyAccessor)property));
-                    }
-                    else
-                    {
-                        initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type));
-                    }
-
-                    visited.Add(property);
-                }
-
-                foreach (AvaloniaProperty property in GetRegisteredAttached(type))
-                {
-                    if (!visited.Contains(property))
-                    {
-                        initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type));
-
-                        visited.Add(property);
-                    }
-                }
-
-                _initializedCache.Add(type, initializationData);
-            }
-
-            foreach (PropertyInitializationData data in initializationData)
-            {
-                data.Property.NotifyInitialized(o);
-            }
-        }
-
         private readonly struct PropertyInitializationData
         {
             public AvaloniaProperty Property { get; }

+ 0 - 15
src/Avalonia.Base/DirectPropertyBase.cs

@@ -101,21 +101,6 @@ namespace Avalonia
             return (DirectPropertyMetadata<TValue>)base.GetMetadata(type);
         }
 
-        /// <inheritdoc/>
-        internal override void NotifyInitialized(IAvaloniaObject o)
-        {
-            if (HasNotifyInitializedObservers)
-            {
-                var e = new AvaloniaPropertyChangedEventArgs<TValue>(
-                    o,
-                    this,
-                    default,
-                    InvokeGetter(o),
-                    BindingPriority.Unset);
-                NotifyInitialized(e);
-            }
-        }
-
         /// <inheritdoc/>
         internal override void RouteClearValue(IAvaloniaObject o)
         {

+ 0 - 15
src/Avalonia.Base/StyledPropertyBase.cs

@@ -181,21 +181,6 @@ namespace Avalonia
         /// <inheritdoc/>
         object IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type);
 
-        /// <inheritdoc/>
-        internal override void NotifyInitialized(IAvaloniaObject o)
-        {
-            if (HasNotifyInitializedObservers)
-            {
-                var e = new AvaloniaPropertyChangedEventArgs<TValue>(
-                    o,
-                    this,
-                    default,
-                    o.GetValue(this),
-                    BindingPriority.Unset);
-                NotifyInitialized(e);
-            }
-        }
-
         /// <inheritdoc/>
         internal override void RouteClearValue(IAvaloniaObject o)
         {

+ 5 - 3
src/Avalonia.Controls.DataGrid/DataGrid.cs

@@ -373,7 +373,11 @@ namespace Avalonia.Controls
         public bool IsValid
         {
             get { return _isValid; }
-            internal set { SetAndRaise(IsValidProperty, ref _isValid, value); }
+            internal set 
+            { 
+                SetAndRaise(IsValidProperty, ref _isValid, value);
+                PseudoClasses.Set(":invalid", !value);
+            }
         }
 
         public static readonly StyledProperty<double> MaxColumnWidthProperty =
@@ -656,8 +660,6 @@ namespace Avalonia.Controls
                 HorizontalScrollBarVisibilityProperty,
                 VerticalScrollBarVisibilityProperty);
 
-            PseudoClass<DataGrid, bool>(IsValidProperty, x => !x, ":invalid");
-
             ItemsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnItemsPropertyChanged(e));
             CanUserResizeColumnsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnCanUserResizeColumnsChanged(e));
             ColumnWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnColumnWidthChanged(e));

+ 24 - 1
src/Avalonia.Controls/Button.cs

@@ -91,7 +91,11 @@ namespace Avalonia.Controls
             CommandProperty.Changed.Subscribe(CommandChanged);
             IsDefaultProperty.Changed.Subscribe(IsDefaultChanged);
             IsCancelProperty.Changed.Subscribe(IsCancelChanged);
-            PseudoClass<Button>(IsPressedProperty, ":pressed");
+        }
+
+        public Button()
+        {
+            UpdatePseudoClasses(IsPressed);
         }
 
         /// <summary>
@@ -312,6 +316,20 @@ namespace Avalonia.Controls
             IsPressed = false;
         }
 
+        protected override void OnPropertyChanged<T>(
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == IsPressedProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<bool>());
+            }
+        }
+
         protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
         {
             base.UpdateDataValidation(property, value);
@@ -474,5 +492,10 @@ namespace Avalonia.Controls
                 OnClick();
             }
         }
+
+        private void UpdatePseudoClasses(bool isPressed)
+        {
+            PseudoClasses.Set(":pressed", isPressed);
+        }
     }
 }

+ 26 - 2
src/Avalonia.Controls/ButtonSpinner.cs

@@ -1,5 +1,6 @@
 using System;
 using Avalonia.Controls.Primitives;
+using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 
@@ -34,6 +35,11 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<Location> ButtonSpinnerLocationProperty =
             AvaloniaProperty.Register<ButtonSpinner, Location>(nameof(ButtonSpinnerLocation), Location.Right);
 
+        public ButtonSpinner()
+        {
+            UpdatePseudoClasses(ButtonSpinnerLocation);
+        }
+
         private Button _decreaseButton;
         /// <summary>
         /// Gets or sets the DecreaseButton template part.
@@ -85,8 +91,6 @@ namespace Avalonia.Controls
         static ButtonSpinner()
         {
             AllowSpinProperty.Changed.Subscribe(AllowSpinChanged);
-            PseudoClass<ButtonSpinner, Location>(ButtonSpinnerLocationProperty, location => location == Location.Left, ":left");
-            PseudoClass<ButtonSpinner, Location>(ButtonSpinnerLocationProperty, location => location == Location.Right, ":right");
         }
 
         /// <summary>
@@ -201,6 +205,20 @@ namespace Avalonia.Controls
             }
         }
 
+        protected override void OnPropertyChanged<T>(
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == ButtonSpinnerLocationProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<Location>());
+            }
+        }
+
         protected override void OnValidSpinDirectionChanged(ValidSpinDirections oldValue, ValidSpinDirections newValue)
         {
             SetButtonUsage();
@@ -259,5 +277,11 @@ namespace Avalonia.Controls
                 OnSpin(new SpinEventArgs(SpinEvent, direction));
             }
         }
+
+        private void UpdatePseudoClasses(Location location)
+        {
+            PseudoClasses.Set(":left", location == Location.Left);
+            PseudoClasses.Set(":right", location == Location.Right);
+        }
     }
 }

+ 0 - 20
src/Avalonia.Controls/ControlExtensions.cs

@@ -69,26 +69,6 @@ namespace Avalonia.Controls
             return nameScope.Find<T>(name);
         }
 
-        /// <summary>
-        /// Adds or removes a pseudoclass depending on a boolean value.
-        /// </summary>
-        /// <param name="classes">The pseudoclasses collection.</param>
-        /// <param name="name">The name of the pseudoclass to set.</param>
-        /// <param name="value">True to add the pseudoclass or false to remove.</param>
-        public static void Set(this IPseudoClasses classes, string name, bool value)
-        {
-            Contract.Requires<ArgumentNullException>(classes != null);
-
-            if (value)
-            {
-                classes.Add(name);
-            }
-            else
-            {
-                classes.Remove(name);
-            }
-        }
-
         /// <summary>
         /// Sets a pseudoclass depending on an observable trigger.
         /// </summary>

+ 33 - 8
src/Avalonia.Controls/Expander.cs

@@ -1,5 +1,6 @@
 using Avalonia.Animation;
 using Avalonia.Controls.Primitives;
+using Avalonia.Data;
 
 namespace Avalonia.Controls
 {
@@ -30,16 +31,14 @@ namespace Avalonia.Controls
 
         static Expander()
         {
-            PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Down, ":down");
-            PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Up, ":up");
-            PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Left, ":left");
-            PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Right, ":right");
-
-            PseudoClass<Expander>(IsExpandedProperty, ":expanded");
-
             IsExpandedProperty.Changed.AddClassHandler<Expander>((x, e) => x.OnIsExpandedChanged(e));
         }
 
+        public Expander()
+        {
+            UpdatePseudoClasses(ExpandDirection);
+        }
+
         public IPageTransition ContentTransition
         {
             get => GetValue(ContentTransitionProperty);
@@ -55,7 +54,11 @@ namespace Avalonia.Controls
         public bool IsExpanded
         {
             get { return _isExpanded; }
-            set { SetAndRaise(IsExpandedProperty, ref _isExpanded, value); }
+            set 
+            { 
+                SetAndRaise(IsExpandedProperty, ref _isExpanded, value);
+                PseudoClasses.Set(":expanded", value);
+            }
         }
 
         protected virtual void OnIsExpandedChanged(AvaloniaPropertyChangedEventArgs e)
@@ -74,5 +77,27 @@ namespace Avalonia.Controls
                 }
             }
         }
+
+        protected override void OnPropertyChanged<T>(
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == ExpandDirectionProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<ExpandDirection>());
+            }
+        }
+
+        private void UpdatePseudoClasses(ExpandDirection d)
+        {
+            PseudoClasses.Set(":up", d == ExpandDirection.Up);
+            PseudoClasses.Set(":down", d == ExpandDirection.Down);
+            PseudoClasses.Set(":left", d == ExpandDirection.Left);
+            PseudoClasses.Set(":right", d == ExpandDirection.Right);
+        }
     }
 }

+ 25 - 5
src/Avalonia.Controls/Notifications/WindowNotificationManager.cs

@@ -8,6 +8,7 @@ using System.Reactive.Linq;
 using System.Threading.Tasks;
 using Avalonia.Controls.Primitives;
 using Avalonia.Rendering;
+using Avalonia.Data;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Controls.Notifications
@@ -68,15 +69,12 @@ namespace Avalonia.Controls.Notifications
                         Install(host);
                     });
             }
+
+            UpdatePseudoClasses(Position);
         }
 
         static WindowNotificationManager()
         {
-            PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.TopLeft, ":topleft");
-            PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.TopRight, ":topright");
-            PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.BottomLeft, ":bottomleft");
-            PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.BottomRight, ":bottomright");
-
             HorizontalAlignmentProperty.OverrideDefaultValue<WindowNotificationManager>(Layout.HorizontalAlignment.Stretch);
             VerticalAlignmentProperty.OverrideDefaultValue<WindowNotificationManager>(Layout.VerticalAlignment.Stretch);
         }
@@ -143,6 +141,20 @@ namespace Avalonia.Controls.Notifications
             notificationControl.Close();
         }
 
+        protected override void OnPropertyChanged<T>(
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == PositionProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<NotificationPosition>());
+            }
+        }
+
         /// <summary>
         /// Installs the <see cref="WindowNotificationManager"/> within the <see cref="AdornerLayer"/>
         /// of the host <see cref="Window"/>.
@@ -155,6 +167,14 @@ namespace Avalonia.Controls.Notifications
             adornerLayer?.Children.Add(this);
         }
 
+        private void UpdatePseudoClasses(NotificationPosition position)
+        {
+            PseudoClasses.Set(":topleft", position == NotificationPosition.TopLeft);
+            PseudoClasses.Set(":topright", position == NotificationPosition.TopRight);
+            PseudoClasses.Set(":bottomleft", position == NotificationPosition.BottomLeft);
+            PseudoClasses.Set(":bottomright", position == NotificationPosition.BottomRight);
+        }
+
         public bool HitTest(Point point) => VisualChildren.HitTestCustom(point);
     }
 }

+ 21 - 3
src/Avalonia.Controls/Primitives/ScrollBar.cs

@@ -55,9 +55,6 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         static ScrollBar()
         {
-            PseudoClass<ScrollBar, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
-            PseudoClass<ScrollBar, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
-
             Thumb.DragDeltaEvent.AddClassHandler<ScrollBar>((x, e) => x.OnThumbDragDelta(e), RoutingStrategies.Bubble);
             Thumb.DragCompletedEvent.AddClassHandler<ScrollBar>((x, e) => x.OnThumbDragComplete(e), RoutingStrategies.Bubble);
         }
@@ -74,6 +71,7 @@ namespace Avalonia.Controls.Primitives
                 this.GetObservable(VisibilityProperty).Select(_ => Unit.Default))
                 .Select(_ => CalculateIsVisible());
             this.Bind(IsVisibleProperty, isVisible, BindingPriority.Style);
+            UpdatePseudoClasses(Orientation);
         }
 
         /// <summary>
@@ -143,6 +141,20 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
+        protected override void OnPropertyChanged<T>(
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == OrientationProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
+            }
+        }
+
         protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
         {
             base.OnTemplateApplied(e);
@@ -252,5 +264,11 @@ namespace Avalonia.Controls.Primitives
         {
             Scroll?.Invoke(this, new ScrollEventArgs(scrollEventType, Value));
         }
+
+        private void UpdatePseudoClasses(Orientation o)
+        {
+            PseudoClasses.Set(":vertical", o == Orientation.Vertical);
+            PseudoClasses.Set(":horizontal", o == Orientation.Horizontal);
+        }
     }
 }

+ 18 - 6
src/Avalonia.Controls/Primitives/ToggleButton.cs

@@ -2,8 +2,8 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using Avalonia.Interactivity;
 using Avalonia.Data;
+using Avalonia.Interactivity;
 
 namespace Avalonia.Controls.Primitives
 {
@@ -51,13 +51,14 @@ namespace Avalonia.Controls.Primitives
 
         static ToggleButton()
         {
-            PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == true, ":checked");
-            PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == false, ":unchecked");
-            PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == null, ":indeterminate");
-
             IsCheckedProperty.Changed.AddClassHandler<ToggleButton>((x, e) => x.OnIsCheckedChanged(e));
         }
 
+        public ToggleButton()
+        {
+            UpdatePseudoClasses(IsChecked);
+        }
+
         /// <summary>
         /// Raised when a <see cref="ToggleButton"/> is checked.
         /// </summary>
@@ -91,7 +92,11 @@ namespace Avalonia.Controls.Primitives
         public bool? IsChecked
         {
             get => _isChecked;
-            set => SetAndRaise(IsCheckedProperty, ref _isChecked, value);
+            set 
+            { 
+                SetAndRaise(IsCheckedProperty, ref _isChecked, value);
+                UpdatePseudoClasses(value);
+            }
         }
 
         /// <summary>
@@ -182,5 +187,12 @@ namespace Avalonia.Controls.Primitives
                     break;
             }
         }
+
+        private void UpdatePseudoClasses(bool? isChecked)
+        {
+            PseudoClasses.Set(":checked", isChecked == true);
+            PseudoClasses.Set(":unchecked", isChecked == false);
+            PseudoClasses.Set(":indeterminate", isChecked == null);
+        }
     }
 }

+ 26 - 2
src/Avalonia.Controls/Primitives/Track.cs

@@ -4,6 +4,7 @@
 // Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
 
 using System;
+using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.Layout;
 using Avalonia.Metadata;
@@ -46,14 +47,17 @@ namespace Avalonia.Controls.Primitives
 
         static Track()
         {
-            PseudoClass<Track, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
-            PseudoClass<Track, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
             ThumbProperty.Changed.AddClassHandler<Track>((x,e) => x.ThumbChanged(e));
             IncreaseButtonProperty.Changed.AddClassHandler<Track>((x, e) => x.ButtonChanged(e));
             DecreaseButtonProperty.Changed.AddClassHandler<Track>((x, e) => x.ButtonChanged(e));
             AffectsArrange<Track>(MinimumProperty, MaximumProperty, ValueProperty, OrientationProperty);
         }
 
+        public Track()
+        {
+            UpdatePseudoClasses(Orientation);
+        }
+
         public double Minimum
         {
             get { return _minimum; }
@@ -276,6 +280,20 @@ namespace Avalonia.Controls.Primitives
             return arrangeSize;
         }
 
+        protected override void OnPropertyChanged<T>(
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == OrientationProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
+            }
+        }
+
         private static void CoerceLength(ref double componentLength, double trackLength)
         {
             if (componentLength < 0)
@@ -433,5 +451,11 @@ namespace Avalonia.Controls.Primitives
                 DecreaseButton.IsVisible = visible;
             }
         }
+
+        private void UpdatePseudoClasses(Orientation o)
+        {
+            PseudoClasses.Set(":vertical", o == Orientation.Vertical);
+            PseudoClasses.Set(":horizontal", o == Orientation.Horizontal);
+        }
     }
 }

+ 40 - 4
src/Avalonia.Controls/ProgressBar.cs

@@ -4,6 +4,7 @@
 
 using System;
 using Avalonia.Controls.Primitives;
+using Avalonia.Data;
 using Avalonia.Layout;
 
 namespace Avalonia.Controls
@@ -35,14 +36,15 @@ namespace Avalonia.Controls
 
         static ProgressBar()
         {
-            PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
-            PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
-            PseudoClass<ProgressBar>(IsIndeterminateProperty, ":indeterminate");
-
             ValueProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
             IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
         }
 
+        public ProgressBar()
+        {
+            UpdatePseudoClasses(IsIndeterminate, Orientation);
+        }
+
         public bool IsIndeterminate
         {
             get => GetValue(IsIndeterminateProperty);
@@ -75,6 +77,24 @@ namespace Avalonia.Controls
             return base.ArrangeOverride(finalSize);
         }
 
+        protected override void OnPropertyChanged<T>(
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == IsIndeterminateProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<bool>(), null);
+            }
+            else if (property == OrientationProperty)
+            {
+                UpdatePseudoClasses(null, newValue.GetValueOrDefault<Orientation>());
+            }
+        }
+
         /// <inheritdoc/>
         protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
         {
@@ -121,5 +141,21 @@ namespace Avalonia.Controls
         {
             UpdateIndicator(Bounds.Size);
         }
+
+        private void UpdatePseudoClasses(
+            bool? isIndeterminate,
+            Orientation? o)
+        {
+            if (isIndeterminate.HasValue)
+            {
+                PseudoClasses.Set(":indeterminate", isIndeterminate.Value);
+            }
+
+            if (o.HasValue)
+            {
+                PseudoClasses.Set(":vertical", o == Orientation.Vertical);
+                PseudoClasses.Set(":horizontal", o == Orientation.Horizontal);
+            }
+        }
     }
 }

+ 22 - 2
src/Avalonia.Controls/Slider.cs

@@ -3,6 +3,7 @@
 
 using System;
 using Avalonia.Controls.Primitives;
+using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Layout;
@@ -43,8 +44,6 @@ namespace Avalonia.Controls
         static Slider()
         {
             OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal);
-            PseudoClass<Slider, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
-            PseudoClass<Slider, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
             Thumb.DragStartedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble);
             Thumb.DragDeltaEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragDelta(e), RoutingStrategies.Bubble);
             Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble);
@@ -55,6 +54,7 @@ namespace Avalonia.Controls
         /// </summary>
         public Slider()
         {
+            UpdatePseudoClasses(Orientation);
         }
 
         /// <summary>
@@ -137,6 +137,20 @@ namespace Avalonia.Controls
             }
         }
 
+        protected override void OnPropertyChanged<T>(
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == OrientationProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
+            }
+        }
+
         /// <summary>
         /// Called when user start dragging the <see cref="Thumb"/>.
         /// </summary>
@@ -190,5 +204,11 @@ namespace Avalonia.Controls
 
             return value;
         }
+
+        private void UpdatePseudoClasses(Orientation o)
+        {
+            PseudoClasses.Set(":vertical", o == Orientation.Vertical);
+            PseudoClasses.Set(":horizontal", o == Orientation.Horizontal);
+        }
     }
 }

+ 38 - 4
src/Avalonia.Input/InputElement.cs

@@ -4,6 +4,8 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Avalonia.Controls;
+using Avalonia.Data;
 using Avalonia.Input.GestureRecognizers;
 using Avalonia.Interactivity;
 using Avalonia.VisualTree;
@@ -181,10 +183,11 @@ namespace Avalonia.Input
             PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e));
             PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureLost(e));
             PointerWheelChangedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerWheelChanged(e));
+        }
 
-            PseudoClass<InputElement, bool>(IsEffectivelyEnabledProperty, x => !x, ":disabled");
-            PseudoClass<InputElement>(IsFocusedProperty, ":focus");
-            PseudoClass<InputElement>(IsPointerOverProperty, ":pointerover");
+        public InputElement()
+        {
+            UpdatePseudoClasses(IsFocused, IsPointerOver);
         }
 
         /// <summary>
@@ -372,7 +375,11 @@ namespace Avalonia.Input
         public bool IsEffectivelyEnabled
         {
             get => _isEffectivelyEnabled;
-            private set => SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value);
+            private set
+            {
+                SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value);
+                PseudoClasses.Set(":disabled", !value);
+            }
         }
 
         public List<KeyBinding> KeyBindings { get; } = new List<KeyBinding>();
@@ -522,6 +529,20 @@ namespace Avalonia.Input
         {
         }
 
+        protected override void OnPropertyChanged<T>(AvaloniaProperty<T> property, Optional<T> oldValue, BindingValue<T> newValue, BindingPriority priority)
+        {
+            base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+            if (property == IsFocusedProperty)
+            {
+                UpdatePseudoClasses(newValue.GetValueOrDefault<bool>(), null);
+            }
+            else if (property == IsPointerOverProperty)
+            {
+                UpdatePseudoClasses(null, newValue.GetValueOrDefault<bool>());
+            }
+        }
+
         /// <summary>
         /// Updates the <see cref="IsEffectivelyEnabled"/> property value according to the parent
         /// control's enabled state and <see cref="IsEnabledCore"/>.
@@ -578,5 +599,18 @@ namespace Avalonia.Input
                 child?.UpdateIsEffectivelyEnabled(this);
             }
         }
+
+        private void UpdatePseudoClasses(bool? isFocused, bool? isPointerOver)
+        {
+            if (isFocused.HasValue)
+            {
+                PseudoClasses.Set(":focus", isFocused.Value);
+            }
+            
+            if (isPointerOver.HasValue)
+            {
+                PseudoClasses.Set(":pointerover", isPointerOver.Value);
+            }
+        }
     }
 }

+ 28 - 0
src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs

@@ -0,0 +1,28 @@
+using System;
+
+namespace Avalonia.Controls
+{
+    public static class PseudolassesExtensions
+    {
+        /// <summary>
+        /// Adds or removes a pseudoclass depending on a boolean value.
+        /// </summary>
+        /// <param name="classes">The pseudoclasses collection.</param>
+        /// <param name="name">The name of the pseudoclass to set.</param>
+        /// <param name="value">True to add the pseudoclass or false to remove.</param>
+        public static void Set(this IPseudoClasses classes, string name, bool value)
+        {
+            Contract.Requires<ArgumentNullException>(classes != null);
+
+            if (value)
+            {
+                classes.Add(name);
+            }
+            else
+            {
+                classes.Remove(name);
+            }
+        }
+
+    }
+}

+ 0 - 77
src/Avalonia.Styling/StyledElement.cs

@@ -488,83 +488,6 @@ namespace Avalonia
             InheritanceParent = parent;
         }
 
-        /// <summary>
-        /// Adds a pseudo-class to be set when a property is true.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <param name="className">The pseudo-class.</param>
-        [Obsolete("Use PseudoClass<TOwner> and specify the control type.")]
-        protected static void PseudoClass(AvaloniaProperty<bool> property, string className)
-        {
-            PseudoClass<StyledElement>(property, className);
-        }
-
-        /// <summary>
-        /// Adds a pseudo-class to be set when a property is true.
-        /// </summary>
-        /// <typeparam name="TOwner">The type to apply the pseudo-class to.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="className">The pseudo-class.</param>
-        protected static void PseudoClass<TOwner>(AvaloniaProperty<bool> property, string className)
-            where TOwner : class, IStyledElement
-        {
-            PseudoClass<TOwner, bool>(property, x => x, className);
-        }
-
-        /// <summary>
-        /// Adds a pseudo-class to be set when a property equals a certain value.
-        /// </summary>
-        /// <typeparam name="TProperty">The type of the property.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="selector">Returns a boolean value based on the property value.</param>
-        /// <param name="className">The pseudo-class.</param>
-        [Obsolete("Use PseudoClass<TOwner, TProperty> and specify the control type.")]
-        protected static void PseudoClass<TProperty>(
-            AvaloniaProperty<TProperty> property,
-            Func<TProperty, bool> selector,
-            string className)
-        {
-            PseudoClass<StyledElement, TProperty>(property, selector, className);
-        }
-
-        /// <summary>
-        /// Adds a pseudo-class to be set when a property equals a certain value.
-        /// </summary>
-        /// <typeparam name="TProperty">The type of the property.</typeparam>
-        /// <typeparam name="TOwner">The type to apply the pseudo-class to.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="selector">Returns a boolean value based on the property value.</param>
-        /// <param name="className">The pseudo-class.</param>
-        protected static void PseudoClass<TOwner, TProperty>(
-            AvaloniaProperty<TProperty> property,
-            Func<TProperty, bool> selector,
-            string className)
-                where TOwner : class, IStyledElement
-        {
-            Contract.Requires<ArgumentNullException>(property != null);
-            Contract.Requires<ArgumentNullException>(selector != null);
-            Contract.Requires<ArgumentNullException>(className != null);
-
-            if (string.IsNullOrWhiteSpace(className))
-            {
-                throw new ArgumentException("Cannot supply an empty className.");
-            }
-
-            property.Changed.Merge(property.Initialized)
-                .Where(e => e.Sender is TOwner)
-                .Subscribe(e =>
-                {
-                    if (selector((TProperty)e.NewValue))
-                    {
-                        ((StyledElement)e.Sender).PseudoClasses.Add(className);
-                    }
-                    else
-                    {
-                        ((StyledElement)e.Sender).PseudoClasses.Remove(className);
-                    }
-                });
-        }
-
         protected virtual void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
         {
             switch (e.Action)

+ 0 - 17
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs

@@ -16,19 +16,6 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal("foodefault", target.GetValue(Class2.FooProperty));
         }
 
-        [Fact]
-        public void AvaloniaProperty_Initialized_Is_Called_For_Attached_Property()
-        {
-            bool raised = false;
-
-            using (Class1.FooProperty.Initialized.Subscribe(x => raised = true))
-            {
-                new Class3();
-            }
-
-            Assert.True(raised);
-        }
-
         private class Base : AvaloniaObject
         {
         }
@@ -46,9 +33,5 @@ namespace Avalonia.Base.UnitTests
             public static readonly AttachedProperty<string> FooProperty =
                 Class1.FooProperty.AddOwner<Class2>();
         }
-
-        private class Class3 : Base
-        {
-        }
     }
 }

+ 0 - 16
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs

@@ -424,22 +424,6 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal("second", target.Foo);
         }
 
-        [Fact]
-        public void Property_Notifies_Initialized()
-        {
-            bool raised = false;
-
-            Class1.FooProperty.Initialized.Subscribe(e =>
-                raised = e.Property == Class1.FooProperty &&
-                         e.OldValue == AvaloniaProperty.UnsetValue &&
-                         (string)e.NewValue == "initial" &&
-                         e.Priority == BindingPriority.Unset);
-
-            var target = new Class1();
-
-            Assert.True(raised);
-        }
-
         [Fact]
         public void Binding_Error_Reverts_To_Default_Value()
         {

+ 0 - 23
tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

@@ -78,24 +78,6 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal(BindingMode.TwoWay, result.DefaultBindingMode);
         }
 
-        [Fact]
-        public void Initialized_Observable_Fired()
-        {
-            bool invoked = false;
-
-            Class1.FooProperty.Initialized.Subscribe(x =>
-            {
-                Assert.Equal(AvaloniaProperty.UnsetValue, x.OldValue);
-                Assert.Equal("default", x.NewValue);
-                Assert.Equal(BindingPriority.Unset, x.Priority);
-                invoked = true;
-            });
-
-            var target = new Class1();
-
-            Assert.True(invoked);
-        }
-
         [Fact]
         public void Changed_Observable_Fired()
         {
@@ -141,11 +123,6 @@ namespace Avalonia.Base.UnitTests
                 OverrideMetadata(typeof(T), metadata);
             }
 
-            internal override void NotifyInitialized(IAvaloniaObject o)
-            {
-                throw new NotImplementedException();
-            }
-
             internal override IDisposable RouteBind(
                 IAvaloniaObject o,
                 IObservable<BindingValue<object>> source,

+ 0 - 22
tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs

@@ -1,33 +1,12 @@
 // Copyright (c) The Avalonia 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.Reactive.Subjects;
-using Avalonia.Data;
 using Xunit;
 
 namespace Avalonia.Base.UnitTests
 {
     public class DirectPropertyTests
     {
-        [Fact]
-        public void Initialized_Observable_Fired()
-        {
-            bool invoked = false;
-
-            Class1.FooProperty.Initialized.Subscribe(x =>
-            {
-                Assert.Equal(AvaloniaProperty.UnsetValue, x.OldValue);
-                Assert.Equal("foo", x.NewValue);
-                Assert.Equal(BindingPriority.Unset, x.Priority);
-                invoked = true;
-            });
-
-            var target = new Class1();
-
-            Assert.True(invoked);
-        }
-
         [Fact]
         public void IsDirect_Property_Returns_True()
         {
@@ -69,7 +48,6 @@ namespace Avalonia.Base.UnitTests
             var p2 = p1.AddOwner<Class2>(o => null, (o, v) => { });
 
             Assert.Same(p1.Changed, p2.Changed);
-            Assert.Same(p1.Initialized, p2.Initialized);
         }
 
         private class Class1 : AvaloniaObject

+ 1 - 1
tests/Avalonia.Benchmarks/Base/AvaloniaObjectInitializationBenchmark.cs

@@ -6,7 +6,7 @@ namespace Avalonia.Benchmarks.Base
     [MemoryDiagnoser]
     public class AvaloniaObjectInitializationBenchmark
     {
-        [Benchmark(OperationsPerInvoke = 1000)]
+        [Benchmark]
         public Button InitializeButton()
         {
             return new Button();