Browse Source

Remove AvaloniaProperty.Initialized.

It was only needed for pseudoclasses. Move pseudoclass registration to an engine that doesn't require `AvaloniaProperty.Initialized`, implemented in `PseudoclassEngine`.
pull/3292/head
Steven Kirk 7 years ago
parent
commit
35bd69f23c
  1. 1
      src/Avalonia.Base/AvaloniaObject.cs
  2. 39
      src/Avalonia.Base/AvaloniaProperty.cs
  3. 45
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  4. 15
      src/Avalonia.Base/DirectPropertyBase.cs
  5. 15
      src/Avalonia.Base/StyledPropertyBase.cs
  6. 120
      src/Avalonia.Styling/PseudoclassEngine.cs
  7. 19
      src/Avalonia.Styling/StyledElement.cs
  8. 17
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs
  9. 16
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
  10. 23
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
  11. 22
      tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs

1
src/Avalonia.Base/AvaloniaObject.cs

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

39
src/Avalonia.Base/AvaloniaProperty.cs

@ -22,7 +22,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;
@ -55,7 +54,6 @@ namespace Avalonia
throw new ArgumentException("'name' may not contain periods.");
}
_initialized = new Subject<AvaloniaPropertyChangedEventArgs>();
_changed = new Subject<AvaloniaPropertyChangedEventArgs>();
_metadata = new Dictionary<Type, PropertyMetadata>();
@ -83,7 +81,6 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(source != null);
Contract.Requires<ArgumentNullException>(ownerType != null);
_initialized = source._initialized;
_changed = source._changed;
_metadata = new Dictionary<Type, PropertyMetadata>();
@ -138,22 +135,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.
@ -482,26 +463,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>

45
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -407,51 +407,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; }

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

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

120
src/Avalonia.Styling/PseudoclassEngine.cs

@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls;
namespace Avalonia
{
internal static class PseudoclassEngine
{
private static Dictionary<Type, List<IEntry>> _entries = new Dictionary<Type, List<IEntry>>();
private static Dictionary<AvaloniaProperty, List<IEntry>> _properties = new Dictionary<AvaloniaProperty, List<IEntry>>();
private static Dictionary<Type, List<IEntry>> _cache = new Dictionary<Type, List<IEntry>>();
public static void Created(StyledElement element)
{
var t = element.GetType();
if (!_cache.TryGetValue(t, out var list))
{
list = new List<IEntry>();
foreach (var i in _entries)
{
if (i.Key.IsAssignableFrom(t))
{
list.AddRange(i.Value);
}
}
_cache.Add(t, list);
}
foreach (var i in list)
{
i.Update(element);
}
}
public static void Register<T>(
Type type,
AvaloniaProperty<T> property,
Func<T, bool> selector,
string className)
{
if (!_entries.TryGetValue(type, out var list))
{
list = new List<IEntry>();
_entries.Add(type, list);
}
var entry = new Entry<T>(type, property, selector, className);
list.Add(entry);
if (!_properties.TryGetValue(property, out list))
{
list = new List<IEntry>();
_properties.Add(property, list);
property.Changed.Subscribe(ValueChanged);
}
list.Add(entry);
}
private static void ValueChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is StyledElement element &&
_properties.TryGetValue(e.Property, out var list))
{
var t = e.Sender.GetType();
foreach (var i in list)
{
if (i.Type.IsAssignableFrom(t))
{
i.Update(element);
}
}
}
}
private interface IEntry
{
Type Type { get; }
void Update(StyledElement element);
}
private class Entry<T> : IEntry
{
public Entry(
Type type,
AvaloniaProperty<T> property,
Func<T, bool> selector,
string className)
{
Type = type;
Property = property;
Selector = selector;
ClassName = className;
}
public Type Type { get; }
public AvaloniaProperty<T> Property { get; }
public Func<T, bool> Selector { get; }
public string ClassName { get; }
public void Update(StyledElement element)
{
var value = element.GetValue(Property);
if (Selector(value))
{
((IPseudoClasses)element.Classes).Add(ClassName);
}
else
{
((IPseudoClasses)element.Classes).Remove(ClassName);
}
}
}
}
}

19
src/Avalonia.Styling/StyledElement.cs

@ -82,6 +82,7 @@ namespace Avalonia
public StyledElement()
{
_isAttachedToLogicalTree = this is IStyleRoot;
PseudoclassEngine.Created(this);
}
/// <summary>
@ -557,19 +558,11 @@ namespace Avalonia
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);
}
});
PseudoclassEngine.Register(
typeof(TOwner),
property,
selector,
className);
}
protected virtual void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)

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
{
}
}
}

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

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,

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

Loading…
Cancel
Save