Browse Source

Remove generics from IAvaloniaObject.

refactor/iavaloniaobject-nongeneric
Steven Kirk 4 years ago
parent
commit
5763a914fe
  1. 53
      src/Avalonia.Base/AvaloniaObject.cs
  2. 234
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  3. 12
      src/Avalonia.Base/AvaloniaProperty.cs
  4. 12
      src/Avalonia.Base/DirectPropertyBase.cs
  5. 104
      src/Avalonia.Base/IAvaloniaObject.cs
  6. 11
      src/Avalonia.Base/PropertyStore/PriorityValue.cs
  7. 2
      src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs
  8. 30
      src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs
  9. 12
      src/Avalonia.Base/StyledPropertyBase.cs
  10. 4
      src/Avalonia.Base/ValueStore.cs
  11. 46
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  12. 4
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  13. 24
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  14. 4
      src/Avalonia.Controls/Repeater/RecyclePool.cs
  15. 6
      src/Avalonia.Controls/Repeater/ViewManager.cs
  16. 4
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs
  17. 2
      src/Avalonia.Input/KeyboardNavigation.cs
  18. 5
      src/Avalonia.Input/Navigation/TabNavigation.cs
  19. 7
      src/Avalonia.Styling/StyledElement.cs
  20. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  21. 12
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
  22. 83
      tests/Avalonia.Markup.UnitTests/Data/BindingTests_TemplatedParent.cs
  23. 49
      tests/Avalonia.Styling.UnitTests/SetterTests.cs

53
src/Avalonia.Base/AvaloniaObject.cs

@ -5,6 +5,7 @@ using Avalonia.Data;
using Avalonia.Diagnostics; using Avalonia.Diagnostics;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.PropertyStore; using Avalonia.PropertyStore;
using Avalonia.Reactive;
using Avalonia.Threading; using Avalonia.Threading;
namespace Avalonia namespace Avalonia
@ -17,11 +18,11 @@ namespace Avalonia
/// </remarks> /// </remarks>
public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged, IValueSink public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged, IValueSink
{ {
private IAvaloniaObject? _inheritanceParent; private AvaloniaObject? _inheritanceParent;
private List<IDisposable>? _directBindings; private List<IDisposable>? _directBindings;
private PropertyChangedEventHandler? _inpcChanged; private PropertyChangedEventHandler? _inpcChanged;
private EventHandler<AvaloniaPropertyChangedEventArgs>? _propertyChanged; private EventHandler<AvaloniaPropertyChangedEventArgs>? _propertyChanged;
private List<IAvaloniaObject>? _inheritanceChildren; private List<AvaloniaObject>? _inheritanceChildren;
private ValueStore? _values; private ValueStore? _values;
private bool _batchUpdate; private bool _batchUpdate;
@ -58,7 +59,7 @@ namespace Avalonia
/// <value> /// <value>
/// The inheritance parent. /// The inheritance parent.
/// </value> /// </value>
protected IAvaloniaObject? InheritanceParent protected AvaloniaObject? InheritanceParent
{ {
get get
{ {
@ -320,14 +321,14 @@ namespace Avalonia
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="priority">The priority of the value.</param> /// <param name="priority">The priority of the value.</param>
public void SetValue( public IDisposable? SetValue(
AvaloniaProperty property, AvaloniaProperty property,
object? value, object? value,
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
{ {
property = property ?? throw new ArgumentNullException(nameof(property)); property = property ?? throw new ArgumentNullException(nameof(property));
property.RouteSetValue(this, value, priority); return property.RouteSetValue(this, value, priority);
} }
/// <summary> /// <summary>
@ -385,6 +386,26 @@ namespace Avalonia
SetDirectValueUnchecked(property, value); SetDirectValueUnchecked(property, value);
} }
/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind(
AvaloniaProperty property,
IObservable<object?> source,
BindingPriority priority = BindingPriority.LocalValue)
{
property = property ?? throw new ArgumentNullException(nameof(property));
source = source ?? throw new ArgumentNullException(nameof(source));
return property.RouteBind(this, source.ToBindingValue(), priority);
}
/// <summary> /// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable. /// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary> /// </summary>
@ -445,9 +466,8 @@ namespace Avalonia
/// <summary> /// <summary>
/// Coerces the specified <see cref="AvaloniaProperty"/>. /// Coerces the specified <see cref="AvaloniaProperty"/>.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
public void CoerceValue<T>(StyledPropertyBase<T> property) public void CoerceValue(AvaloniaProperty property)
{ {
_values?.CoerceValue(property); _values?.CoerceValue(property);
} }
@ -475,19 +495,19 @@ namespace Avalonia
} }
/// <inheritdoc/> /// <inheritdoc/>
void IAvaloniaObject.AddInheritanceChild(IAvaloniaObject child) internal void AddInheritanceChild(AvaloniaObject child)
{ {
_inheritanceChildren ??= new List<IAvaloniaObject>(); _inheritanceChildren ??= new List<AvaloniaObject>();
_inheritanceChildren.Add(child); _inheritanceChildren.Add(child);
} }
/// <inheritdoc/> /// <inheritdoc/>
void IAvaloniaObject.RemoveInheritanceChild(IAvaloniaObject child) internal void RemoveInheritanceChild(AvaloniaObject child)
{ {
_inheritanceChildren?.Remove(child); _inheritanceChildren?.Remove(child);
} }
void IAvaloniaObject.InheritedPropertyChanged<T>( internal void InheritedPropertyChanged<T>(
AvaloniaProperty<T> property, AvaloniaProperty<T> property,
Optional<T> oldValue, Optional<T> oldValue,
Optional<T> newValue) Optional<T> newValue)
@ -565,14 +585,11 @@ namespace Avalonia
/// <param name="oldParent">The old inheritance parent.</param> /// <param name="oldParent">The old inheritance parent.</param>
internal void InheritanceParentChanged<T>( internal void InheritanceParentChanged<T>(
StyledPropertyBase<T> property, StyledPropertyBase<T> property,
IAvaloniaObject? oldParent) AvaloniaObject? oldParent)
{ {
var oldValue = oldParent switch var oldValue = oldParent is not null ?
{ oldParent.GetValueOrInheritedOrDefault(property) :
AvaloniaObject o => o.GetValueOrInheritedOrDefault(property), property.GetDefaultValue(GetType());
null => property.GetDefaultValue(GetType()),
_ => oldParent.GetValue(property)
};
var newValue = GetInheritedOrDefault(property); var newValue = GetInheritedOrDefault(property);

234
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -25,7 +25,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets an observable for a <see cref="AvaloniaProperty"/>. /// Gets an observable for an <see cref="AvaloniaProperty"/>.
/// </summary> /// </summary>
/// <param name="o">The object.</param> /// <param name="o">The object.</param>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
@ -44,7 +44,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets an observable for a <see cref="AvaloniaProperty"/>. /// Gets an observable for an <see cref="AvaloniaProperty"/>.
/// </summary> /// </summary>
/// <param name="o">The object.</param> /// <param name="o">The object.</param>
/// <typeparam name="T">The property type.</typeparam> /// <typeparam name="T">The property type.</typeparam>
@ -64,7 +64,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets an observable for a <see cref="AvaloniaProperty"/>. /// Gets an observable for an <see cref="AvaloniaProperty"/>.
/// </summary> /// </summary>
/// <param name="o">The object.</param> /// <param name="o">The object.</param>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
@ -85,7 +85,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets an observable for a <see cref="AvaloniaProperty"/>. /// Gets an observable for an <see cref="AvaloniaProperty"/>.
/// </summary> /// </summary>
/// <param name="o">The object.</param> /// <param name="o">The object.</param>
/// <typeparam name="T">The property type.</typeparam> /// <typeparam name="T">The property type.</typeparam>
@ -128,7 +128,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets a subject for a <see cref="AvaloniaProperty"/>. /// Gets a subject for an <see cref="AvaloniaProperty"/>.
/// </summary> /// </summary>
/// <param name="o">The object.</param> /// <param name="o">The object.</param>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
@ -150,7 +150,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets a subject for a <see cref="AvaloniaProperty"/>. /// Gets a subject for an <see cref="AvaloniaProperty"/>.
/// </summary> /// </summary>
/// <typeparam name="T">The property type.</typeparam> /// <typeparam name="T">The property type.</typeparam>
/// <param name="o">The object.</param> /// <param name="o">The object.</param>
@ -230,30 +230,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable. /// Binds an <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
/// <param name="target">The object.</param>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
public static IDisposable Bind(
this IAvaloniaObject target,
AvaloniaProperty property,
IObservable<BindingValue<object?>> source,
BindingPriority priority = BindingPriority.LocalValue)
{
target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property));
source = source ?? throw new ArgumentNullException(nameof(source));
return property.RouteBind(target, source, priority);
}
/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the property.</typeparam> /// <typeparam name="T">The type of the property.</typeparam>
/// <param name="target">The object.</param> /// <param name="target">The object.</param>
@ -273,42 +250,22 @@ namespace Avalonia
property = property ?? throw new ArgumentNullException(nameof(property)); property = property ?? throw new ArgumentNullException(nameof(property));
source = source ?? throw new ArgumentNullException(nameof(source)); source = source ?? throw new ArgumentNullException(nameof(source));
return property switch if (target is AvaloniaObject ao)
{ {
StyledPropertyBase<T> styled => target.Bind(styled, source, priority), return property switch
DirectPropertyBase<T> direct => target.Bind(direct, source), {
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), StyledPropertyBase<T> styled => ao.Bind(styled, source, priority),
}; DirectPropertyBase<T> direct => ao.Bind(direct, source),
} _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
};
}
/// <summary> throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
/// <param name="target">The object.</param>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
public static IDisposable Bind(
this IAvaloniaObject target,
AvaloniaProperty property,
IObservable<object?> source,
BindingPriority priority = BindingPriority.LocalValue)
{
target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property));
source = source ?? throw new ArgumentNullException(nameof(source));
return target.Bind(
property,
source.ToBindingValue(),
priority);
} }
/// <summary> /// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable. /// Binds an <see cref="AvaloniaProperty"/> to an observable.
/// </summary> /// </summary>
/// <param name="target">The object.</param> /// <param name="target">The object.</param>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
@ -334,7 +291,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Binds a property on an <see cref="IAvaloniaObject"/> to an <see cref="IBinding"/>. /// Binds a property on an <see cref="AvaloniaObject"/> to an <see cref="IBinding"/>.
/// </summary> /// </summary>
/// <param name="target">The object.</param> /// <param name="target">The object.</param>
/// <param name="property">The property to bind.</param> /// <param name="property">The property to bind.</param>
@ -374,56 +331,6 @@ namespace Avalonia
} }
} }
/// <summary>
/// Clears a <see cref="AvaloniaProperty"/>'s local value.
/// </summary>
/// <param name="target">The object.</param>
/// <param name="property">The property.</param>
public static void ClearValue(this IAvaloniaObject target, AvaloniaProperty property)
{
target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property));
property.RouteClearValue(target);
}
/// <summary>
/// Clears a <see cref="AvaloniaProperty"/>'s local value.
/// </summary>
/// <param name="target">The object.</param>
/// <param name="property">The property.</param>
public static void ClearValue<T>(this IAvaloniaObject target, AvaloniaProperty<T> property)
{
target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property));
switch (property)
{
case StyledPropertyBase<T> styled:
target.ClearValue(styled);
break;
case DirectPropertyBase<T> direct:
target.ClearValue(direct);
break;
default:
throw new NotSupportedException("Unsupported AvaloniaProperty type.");
}
}
/// <summary>
/// Gets a <see cref="AvaloniaProperty"/> value.
/// </summary>
/// <param name="target">The object.</param>
/// <param name="property">The property.</param>
/// <returns>The value.</returns>
public static object? GetValue(this IAvaloniaObject target, AvaloniaProperty property)
{
target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property));
return property.RouteGetValue(target);
}
/// <summary> /// <summary>
/// Gets a <see cref="AvaloniaProperty"/> value. /// Gets a <see cref="AvaloniaProperty"/> value.
/// </summary> /// </summary>
@ -436,12 +343,18 @@ namespace Avalonia
target = target ?? throw new ArgumentNullException(nameof(target)); target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property)); property = property ?? throw new ArgumentNullException(nameof(property));
return property switch if (target is AvaloniaObject ao)
{ {
StyledPropertyBase<T> styled => target.GetValue(styled), return property switch
DirectPropertyBase<T> direct => target.GetValue(direct), {
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") StyledPropertyBase<T> styled => ao.GetValue(styled),
}; DirectPropertyBase<T> direct => ao.GetValue(direct),
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
};
}
throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
} }
/// <summary> /// <summary>
@ -456,7 +369,7 @@ namespace Avalonia
/// <see cref="AvaloniaProperty.UnsetValue"/>. Note that this method does not return /// <see cref="AvaloniaProperty.UnsetValue"/>. Note that this method does not return
/// property values that come from inherited or default values. /// property values that come from inherited or default values.
/// ///
/// For direct properties returns <see cref="GetValue(IAvaloniaObject, AvaloniaProperty)"/>. /// For direct properties returns the current value of the property.
/// </remarks> /// </remarks>
public static object? GetBaseValue( public static object? GetBaseValue(
this IAvaloniaObject target, this IAvaloniaObject target,
@ -466,7 +379,9 @@ namespace Avalonia
target = target ?? throw new ArgumentNullException(nameof(target)); target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property)); property = property ?? throw new ArgumentNullException(nameof(property));
return property.RouteGetBaseValue(target, maxPriority); if (target is AvaloniaObject ao)
return property.RouteGetBaseValue(ao, maxPriority);
throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
} }
/// <summary> /// <summary>
@ -481,8 +396,7 @@ namespace Avalonia
/// <see cref="Optional{T}.Empty"/>. Note that this method does not return property values /// <see cref="Optional{T}.Empty"/>. Note that this method does not return property values
/// that come from inherited or default values. /// that come from inherited or default values.
/// ///
/// For direct properties returns /// For direct properties returns the current value of the property.
/// <see cref="IAvaloniaObject.GetValue{T}(DirectPropertyBase{T})"/>.
/// </remarks> /// </remarks>
public static Optional<T> GetBaseValue<T>( public static Optional<T> GetBaseValue<T>(
this IAvaloniaObject target, this IAvaloniaObject target,
@ -492,69 +406,18 @@ namespace Avalonia
target = target ?? throw new ArgumentNullException(nameof(target)); target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property)); property = property ?? throw new ArgumentNullException(nameof(property));
target = target ?? throw new ArgumentNullException(nameof(target)); if (target is AvaloniaObject ao)
property = property ?? throw new ArgumentNullException(nameof(property));
return property switch
{ {
StyledPropertyBase<T> styled => target.GetBaseValue(styled, maxPriority), return property switch
DirectPropertyBase<T> direct => target.GetValue(direct), {
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") StyledPropertyBase<T> styled => ao.GetBaseValue(styled, maxPriority),
}; DirectPropertyBase<T> direct => ao.GetValue(direct),
} _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
};
/// <summary>
/// Sets a <see cref="AvaloniaProperty"/> value.
/// </summary>
/// <param name="target">The object.</param>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
/// <param name="priority">The priority of the value.</param>
/// <returns>
/// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
/// </returns>
public static IDisposable? SetValue(
this IAvaloniaObject target,
AvaloniaProperty property,
object? value,
BindingPriority priority = BindingPriority.LocalValue)
{
target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property));
return property.RouteSetValue(target, value, priority);
}
/// <summary>
/// Sets a <see cref="AvaloniaProperty"/> value.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="target">The object.</param>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
/// <param name="priority">The priority of the value.</param>
/// <returns>
/// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
/// </returns>
public static IDisposable? SetValue<T>(
this IAvaloniaObject target,
AvaloniaProperty<T> property,
T value,
BindingPriority priority = BindingPriority.LocalValue)
{
target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property));
switch (property)
{
case StyledPropertyBase<T> styled:
return target.SetValue(styled, value, priority);
case DirectPropertyBase<T> direct:
target.SetValue(direct, value);
return null;
default:
throw new NotSupportedException("Unsupported AvaloniaProperty type.");
} }
throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
} }
/// <summary> /// <summary>
@ -622,17 +485,6 @@ namespace Avalonia
return observable.Subscribe(e => SubscribeAdapter(e, handler)); return observable.Subscribe(e => SubscribeAdapter(e, handler));
} }
/// <summary>
/// Gets a description of a property that van be used in observables.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="property">The property</param>
/// <returns>The description.</returns>
private static string GetDescription(IAvaloniaObject o, AvaloniaProperty property)
{
return $"{o.GetType().Name}.{property.Name}";
}
/// <summary> /// <summary>
/// Observer method for <see cref="AddClassHandler{TTarget}(IObservable{AvaloniaPropertyChangedEventArgs}, /// Observer method for <see cref="AddClassHandler{TTarget}(IObservable{AvaloniaPropertyChangedEventArgs},
/// Func{TTarget, Action{AvaloniaPropertyChangedEventArgs}})"/>. /// Func{TTarget, Action{AvaloniaPropertyChangedEventArgs}})"/>.

12
src/Avalonia.Base/AvaloniaProperty.cs

@ -467,20 +467,20 @@ namespace Avalonia
/// Routes an untyped ClearValue call to a typed call. /// Routes an untyped ClearValue call to a typed call.
/// </summary> /// </summary>
/// <param name="o">The object instance.</param> /// <param name="o">The object instance.</param>
internal abstract void RouteClearValue(IAvaloniaObject o); internal abstract void RouteClearValue(AvaloniaObject o);
/// <summary> /// <summary>
/// Routes an untyped GetValue call to a typed call. /// Routes an untyped GetValue call to a typed call.
/// </summary> /// </summary>
/// <param name="o">The object instance.</param> /// <param name="o">The object instance.</param>
internal abstract object? RouteGetValue(IAvaloniaObject o); internal abstract object? RouteGetValue(AvaloniaObject o);
/// <summary> /// <summary>
/// Routes an untyped GetBaseValue call to a typed call. /// Routes an untyped GetBaseValue call to a typed call.
/// </summary> /// </summary>
/// <param name="o">The object instance.</param> /// <param name="o">The object instance.</param>
/// <param name="maxPriority">The maximum priority for the value.</param> /// <param name="maxPriority">The maximum priority for the value.</param>
internal abstract object? RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority); internal abstract object? RouteGetBaseValue(AvaloniaObject o, BindingPriority maxPriority);
/// <summary> /// <summary>
/// Routes an untyped SetValue call to a typed call. /// Routes an untyped SetValue call to a typed call.
@ -492,7 +492,7 @@ namespace Avalonia
/// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null. /// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
/// </returns> /// </returns>
internal abstract IDisposable? RouteSetValue( internal abstract IDisposable? RouteSetValue(
IAvaloniaObject o, AvaloniaObject o,
object? value, object? value,
BindingPriority priority); BindingPriority priority);
@ -503,11 +503,11 @@ namespace Avalonia
/// <param name="source">The binding source.</param> /// <param name="source">The binding source.</param>
/// <param name="priority">The priority.</param> /// <param name="priority">The priority.</param>
internal abstract IDisposable RouteBind( internal abstract IDisposable RouteBind(
IAvaloniaObject o, AvaloniaObject o,
IObservable<BindingValue<object?>> source, IObservable<BindingValue<object?>> source,
BindingPriority priority); BindingPriority priority);
internal abstract void RouteInheritanceParentChanged(AvaloniaObject o, IAvaloniaObject? oldParent); internal abstract void RouteInheritanceParentChanged(AvaloniaObject o, AvaloniaObject? oldParent);
/// <summary> /// <summary>
/// Overrides the metadata for the property on the specified type. /// Overrides the metadata for the property on the specified type.

12
src/Avalonia.Base/DirectPropertyBase.cs

@ -127,25 +127,25 @@ namespace Avalonia
} }
/// <inheritdoc/> /// <inheritdoc/>
internal override void RouteClearValue(IAvaloniaObject o) internal override void RouteClearValue(AvaloniaObject o)
{ {
o.ClearValue<TValue>(this); o.ClearValue<TValue>(this);
} }
/// <inheritdoc/> /// <inheritdoc/>
internal override object? RouteGetValue(IAvaloniaObject o) internal override object? RouteGetValue(AvaloniaObject o)
{ {
return o.GetValue<TValue>(this); return o.GetValue<TValue>(this);
} }
internal override object? RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority) internal override object? RouteGetBaseValue(AvaloniaObject o, BindingPriority maxPriority)
{ {
return o.GetValue<TValue>(this); return o.GetValue<TValue>(this);
} }
/// <inheritdoc/> /// <inheritdoc/>
internal override IDisposable? RouteSetValue( internal override IDisposable? RouteSetValue(
IAvaloniaObject o, AvaloniaObject o,
object? value, object? value,
BindingPriority priority) BindingPriority priority)
{ {
@ -169,7 +169,7 @@ namespace Avalonia
/// <inheritdoc/> /// <inheritdoc/>
internal override IDisposable RouteBind( internal override IDisposable RouteBind(
IAvaloniaObject o, AvaloniaObject o,
IObservable<BindingValue<object?>> source, IObservable<BindingValue<object?>> source,
BindingPriority priority) BindingPriority priority)
{ {
@ -177,7 +177,7 @@ namespace Avalonia
return o.Bind<TValue>(this, adapter); return o.Bind<TValue>(this, adapter);
} }
internal override void RouteInheritanceParentChanged(AvaloniaObject o, IAvaloniaObject? oldParent) internal override void RouteInheritanceParentChanged(AvaloniaObject o, AvaloniaObject? oldParent)
{ {
throw new NotSupportedException("Direct properties do not support inheritance."); throw new NotSupportedException("Direct properties do not support inheritance.");
} }

104
src/Avalonia.Base/IAvaloniaObject.cs

@ -17,42 +17,14 @@ namespace Avalonia
/// Clears an <see cref="AvaloniaProperty"/>'s local value. /// Clears an <see cref="AvaloniaProperty"/>'s local value.
/// </summary> /// </summary>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
void ClearValue<T>(StyledPropertyBase<T> property); void ClearValue(AvaloniaProperty property);
/// <summary>
/// Clears an <see cref="AvaloniaProperty"/>'s local value.
/// </summary>
/// <param name="property">The property.</param>
void ClearValue<T>(DirectPropertyBase<T> property);
/// <summary>
/// Gets a <see cref="AvaloniaProperty"/> value.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <returns>The value.</returns>
T GetValue<T>(StyledPropertyBase<T> property);
/// <summary> /// <summary>
/// Gets a <see cref="AvaloniaProperty"/> value. /// Gets a <see cref="AvaloniaProperty"/> value.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <returns>The value.</returns> /// <returns>The value.</returns>
T GetValue<T>(DirectPropertyBase<T> property); object? GetValue(AvaloniaProperty property);
/// <summary>
/// Gets an <see cref="AvaloniaProperty"/> base value.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="maxPriority">The maximum priority for the value.</param>
/// <remarks>
/// Gets the value of the property, if set on this object with a priority equal or lower to
/// <paramref name="maxPriority"/>, otherwise <see cref="Optional{T}.Empty"/>. Note that
/// this method does not return property values that come from inherited or default values.
/// </remarks>
Optional<T> GetBaseValue<T>(StyledPropertyBase<T> property, BindingPriority maxPriority);
/// <summary> /// <summary>
/// Checks whether a <see cref="AvaloniaProperty"/> is animating. /// Checks whether a <see cref="AvaloniaProperty"/> is animating.
@ -71,93 +43,35 @@ namespace Avalonia
/// <summary> /// <summary>
/// Sets a <see cref="AvaloniaProperty"/> value. /// Sets a <see cref="AvaloniaProperty"/> value.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="priority">The priority of the value.</param> /// <param name="priority">The priority of the value.</param>
/// <returns> /// <returns>
/// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null. /// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
/// </returns> /// </returns>
IDisposable? SetValue<T>( IDisposable? SetValue(
StyledPropertyBase<T> property, AvaloniaProperty property,
T value, object? value,
BindingPriority priority = BindingPriority.LocalValue); BindingPriority priority = BindingPriority.LocalValue);
/// <summary>
/// Sets a <see cref="AvaloniaProperty"/> value.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
void SetValue<T>(DirectPropertyBase<T> property, T value);
/// <summary> /// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable. /// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <param name="source">The observable.</param> /// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param> /// <param name="priority">The priority of the binding.</param>
/// <returns> /// <returns>
/// A disposable which can be used to terminate the binding. /// A disposable which can be used to terminate the binding.
/// </returns> /// </returns>
IDisposable Bind<T>( IDisposable Bind(
StyledPropertyBase<T> property, AvaloniaProperty property,
IObservable<BindingValue<T>> source, IObservable<object?> source,
BindingPriority priority = BindingPriority.LocalValue); BindingPriority priority = BindingPriority.LocalValue);
/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
IDisposable Bind<T>(
DirectPropertyBase<T> property,
IObservable<BindingValue<T>> source);
/// <summary> /// <summary>
/// Coerces the specified <see cref="AvaloniaProperty"/>. /// Coerces the specified <see cref="AvaloniaProperty"/>.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
void CoerceValue<T>(StyledPropertyBase<T> property); void CoerceValue(AvaloniaProperty property);
/// <summary>
/// Registers an object as an inheritance child.
/// </summary>
/// <param name="child">The inheritance child.</param>
/// <remarks>
/// Inheritance children will receive a call to
/// <see cref="InheritedPropertyChanged{T}(AvaloniaProperty{T}, Optional{T}, Optional{T})"/>
/// when an inheritable property value changes on the parent.
/// </remarks>
void AddInheritanceChild(IAvaloniaObject child);
/// <summary>
/// Unregisters an object as an inheritance child.
/// </summary>
/// <param name="child">The inheritance child.</param>
/// <remarks>
/// Removes an inheritance child that was added by a call to
/// <see cref="AddInheritanceChild(IAvaloniaObject)"/>.
/// </remarks>
void RemoveInheritanceChild(IAvaloniaObject child);
/// <summary>
/// Called when an inheritable property changes on an object registered as an inheritance
/// parent.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="property">The property that has changed.</param>
/// <param name="oldValue"></param>
/// <param name="newValue"></param>
void InheritedPropertyChanged<T>(
AvaloniaProperty<T> property,
Optional<T> oldValue,
Optional<T> newValue);
} }
} }

11
src/Avalonia.Base/PropertyStore/PriorityValue.cs

@ -1,10 +1,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using Avalonia.Data; using Avalonia.Data;
namespace Avalonia.PropertyStore namespace Avalonia.PropertyStore
{ {
/// <summary>
/// Represents an untyped interface to <see cref="PriorityValue{T}"/>.
/// </summary>
interface IPriorityValue : IValue
{
void UpdateEffectiveValue();
}
/// <summary> /// <summary>
/// Stores a set of prioritized values and bindings in a <see cref="ValueStore"/>. /// Stores a set of prioritized values and bindings in a <see cref="ValueStore"/>.
/// </summary> /// </summary>
@ -16,7 +23,7 @@ namespace Avalonia.PropertyStore
/// <see cref="IPriorityValueEntry{T}"/> entries (sorted first by priority and then in the order /// <see cref="IPriorityValueEntry{T}"/> entries (sorted first by priority and then in the order
/// they were added) plus a local value. /// they were added) plus a local value.
/// </remarks> /// </remarks>
internal class PriorityValue<T> : IValue<T>, IValueSink, IBatchUpdate internal class PriorityValue<T> : IPriorityValue, IValue<T>, IValueSink, IBatchUpdate
{ {
private readonly IAvaloniaObject _owner; private readonly IAvaloniaObject _owner;
private readonly IValueSink _sink; private readonly IValueSink _sink;

2
src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs

@ -51,7 +51,7 @@ namespace Avalonia.Reactive
{ {
if (e is AvaloniaPropertyChangedEventArgs<T> typedArgs) if (e is AvaloniaPropertyChangedEventArgs<T> typedArgs)
{ {
var newValue = e.Sender.GetValue(typedArgs.Property); var newValue = e.Sender.GetValue<T>(typedArgs.Property);
if (!_value.HasValue || !EqualityComparer<T>.Default.Equals(newValue, _value.Value)) if (!_value.HasValue || !EqualityComparer<T>.Default.Equals(newValue, _value.Value))
{ {

30
src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs

@ -49,23 +49,31 @@ namespace Avalonia.Reactive
{ {
if (e.Property == _property) if (e.Property == _property)
{ {
T newValue; if (e.Sender is AvaloniaObject ao)
if (e is AvaloniaPropertyChangedEventArgs<T> typed)
{ {
newValue = typed.Sender.GetValue(typed.Property); T newValue;
if (e is AvaloniaPropertyChangedEventArgs<T> typed)
{
newValue = AvaloniaObjectExtensions.GetValue(ao, typed.Property);
}
else
{
newValue = (T)e.Sender.GetValue(e.Property)!;
}
if (!_value.HasValue ||
!EqualityComparer<T>.Default.Equals(newValue, _value.Value))
{
_value = newValue;
PublishNext(_value.Value!);
}
} }
else else
{ {
newValue = (T)e.Sender.GetValue(e.Property)!; throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
} }
if (!_value.HasValue ||
!EqualityComparer<T>.Default.Equals(newValue, _value.Value))
{
_value = newValue;
PublishNext(_value.Value!);
}
} }
} }
} }

12
src/Avalonia.Base/StyledPropertyBase.cs

@ -177,19 +177,19 @@ namespace Avalonia
object? IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type); object? IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type);
/// <inheritdoc/> /// <inheritdoc/>
internal override void RouteClearValue(IAvaloniaObject o) internal override void RouteClearValue(AvaloniaObject o)
{ {
o.ClearValue<TValue>(this); o.ClearValue<TValue>(this);
} }
/// <inheritdoc/> /// <inheritdoc/>
internal override object? RouteGetValue(IAvaloniaObject o) internal override object? RouteGetValue(AvaloniaObject o)
{ {
return o.GetValue<TValue>(this); return o.GetValue<TValue>(this);
} }
/// <inheritdoc/> /// <inheritdoc/>
internal override object? RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority) internal override object? RouteGetBaseValue(AvaloniaObject o, BindingPriority maxPriority)
{ {
var value = o.GetBaseValue<TValue>(this, maxPriority); var value = o.GetBaseValue<TValue>(this, maxPriority);
return value.HasValue ? value.Value : AvaloniaProperty.UnsetValue; return value.HasValue ? value.Value : AvaloniaProperty.UnsetValue;
@ -197,7 +197,7 @@ namespace Avalonia
/// <inheritdoc/> /// <inheritdoc/>
internal override IDisposable? RouteSetValue( internal override IDisposable? RouteSetValue(
IAvaloniaObject o, AvaloniaObject o,
object? value, object? value,
BindingPriority priority) BindingPriority priority)
{ {
@ -221,7 +221,7 @@ namespace Avalonia
/// <inheritdoc/> /// <inheritdoc/>
internal override IDisposable RouteBind( internal override IDisposable RouteBind(
IAvaloniaObject o, AvaloniaObject o,
IObservable<BindingValue<object?>> source, IObservable<BindingValue<object?>> source,
BindingPriority priority) BindingPriority priority)
{ {
@ -232,7 +232,7 @@ namespace Avalonia
/// <inheritdoc/> /// <inheritdoc/>
internal override void RouteInheritanceParentChanged( internal override void RouteInheritanceParentChanged(
AvaloniaObject o, AvaloniaObject o,
IAvaloniaObject? oldParent) AvaloniaObject? oldParent)
{ {
o.InheritanceParentChanged(this, oldParent); o.InheritanceParentChanged(this, oldParent);
} }

4
src/Avalonia.Base/ValueStore.cs

@ -196,11 +196,11 @@ namespace Avalonia
} }
} }
public void CoerceValue<T>(StyledPropertyBase<T> property) public void CoerceValue(AvaloniaProperty property)
{ {
if (TryGetValue(property, out var slot)) if (TryGetValue(property, out var slot))
{ {
if (slot is PriorityValue<T> p) if (slot is IPriorityValue p)
{ {
p.UpdateEffectiveValue(); p.UpdateEffectiveValue();
} }

46
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -69,15 +69,18 @@ namespace Avalonia.Controls.Primitives
{ {
foreach (var child in Children) foreach (var child in Children)
{ {
var info = child.GetValue(s_adornedElementInfoProperty); if (child is AvaloniaObject ao)
if (info != null && info.Bounds.HasValue)
{
child.Measure(info.Bounds.Value.Bounds.Size);
}
else
{ {
child.Measure(availableSize); var info = ao.GetValue(s_adornedElementInfoProperty);
if (info != null && info.Bounds.HasValue)
{
child.Measure(info.Bounds.Value.Bounds.Size);
}
else
{
child.Measure(availableSize);
}
} }
} }
@ -88,19 +91,22 @@ namespace Avalonia.Controls.Primitives
{ {
foreach (var child in Children) foreach (var child in Children)
{ {
var info = child.GetValue(s_adornedElementInfoProperty); if (child is AvaloniaObject ao)
var isClipEnabled = child.GetValue(IsClipEnabledProperty);
if (info != null && info.Bounds.HasValue)
{
child.RenderTransform = new MatrixTransform(info.Bounds.Value.Transform);
child.RenderTransformOrigin = new RelativePoint(new Point(0,0), RelativeUnit.Absolute);
UpdateClip(child, info.Bounds.Value, isClipEnabled);
child.Arrange(info.Bounds.Value.Bounds);
}
else
{ {
child.Arrange(new Rect(finalSize)); var info = ao.GetValue(s_adornedElementInfoProperty);
var isClipEnabled = ao.GetValue(IsClipEnabledProperty);
if (info != null && info.Bounds.HasValue)
{
child.RenderTransform = new MatrixTransform(info.Bounds.Value.Transform);
child.RenderTransformOrigin = new RelativePoint(new Point(0, 0), RelativeUnit.Absolute);
UpdateClip(child, info.Bounds.Value, isClipEnabled);
child.Arrange(info.Bounds.Value.Bounds);
}
else
{
child.Arrange(new Rect(finalSize));
}
} }
} }

4
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -533,9 +533,9 @@ namespace Avalonia.Controls.Primitives
bool Match(ItemContainerInfo info) bool Match(ItemContainerInfo info)
{ {
if (info.ContainerControl.IsSet(TextSearch.TextProperty)) if (info.ContainerControl is AvaloniaObject ao && ao.IsSet(TextSearch.TextProperty))
{ {
var searchText = info.ContainerControl.GetValue(TextSearch.TextProperty); var searchText = ao.GetValue(TextSearch.TextProperty);
if (searchText?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true) if (searchText?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true)
{ {

24
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@ -257,10 +257,9 @@ namespace Avalonia.Controls
internal void UnpinElement(IControl element) => _viewManager.UpdatePin(element, false); internal void UnpinElement(IControl element) => _viewManager.UpdatePin(element, false);
internal static VirtualizationInfo TryGetVirtualizationInfo(IControl element) internal static VirtualizationInfo? TryGetVirtualizationInfo(IControl element)
{ {
var value = element.GetValue(VirtualizationInfoProperty); return (element as AvaloniaObject)?.GetValue(VirtualizationInfoProperty);
return value;
} }
internal static VirtualizationInfo CreateAndInitializeVirtualizationInfo(IControl element) internal static VirtualizationInfo CreateAndInitializeVirtualizationInfo(IControl element)
@ -277,15 +276,20 @@ namespace Avalonia.Controls
internal static VirtualizationInfo GetVirtualizationInfo(IControl element) internal static VirtualizationInfo GetVirtualizationInfo(IControl element)
{ {
var result = element.GetValue(VirtualizationInfoProperty); if (element is AvaloniaObject ao)
if (result == null)
{ {
result = new VirtualizationInfo(); var result = ao.GetValue(VirtualizationInfoProperty);
element.SetValue(VirtualizationInfoProperty, result);
if (result == null)
{
result = new VirtualizationInfo();
ao.SetValue(VirtualizationInfoProperty, result);
}
return result;
} }
return result; throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
} }
private protected override void InvalidateMeasureOnChildrenChanged() private protected override void InvalidateMeasureOnChildrenChanged()
@ -491,7 +495,7 @@ namespace Avalonia.Controls
if (parent == this) if (parent == this)
{ {
var virtInfo = TryGetVirtualizationInfo(element); var virtInfo = TryGetVirtualizationInfo(element);
return _viewManager.GetElementIndex(virtInfo); return _viewManager.GetElementIndex(virtInfo!);
} }
return -1; return -1;

4
src/Avalonia.Controls/Repeater/RecyclePool.cs

@ -80,8 +80,8 @@ namespace Avalonia.Controls
return null; return null;
} }
internal string GetReuseKey(IControl element) => element.GetValue(ReuseKeyProperty); internal string GetReuseKey(IControl element) => ((Control)element).GetValue(ReuseKeyProperty);
internal void SetReuseKey(IControl element, string value) => element.SetValue(ReuseKeyProperty, value); internal void SetReuseKey(IControl element, string value) => ((Control)element).SetValue(ReuseKeyProperty, value);
private IPanel? EnsureOwnerIsPanelOrNull(IControl? owner) private IPanel? EnsureOwnerIsPanelOrNull(IControl? owner)
{ {

6
src/Avalonia.Controls/Repeater/ViewManager.cs

@ -47,7 +47,7 @@ namespace Avalonia.Controls
if (madeAnchor != null) if (madeAnchor != null)
{ {
var anchorVirtInfo = ItemsRepeater.TryGetVirtualizationInfo(madeAnchor); var anchorVirtInfo = ItemsRepeater.TryGetVirtualizationInfo(madeAnchor);
if (anchorVirtInfo.Index == index) if (anchorVirtInfo!.Index == index)
{ {
element = madeAnchor; element = madeAnchor;
} }
@ -60,12 +60,12 @@ namespace Avalonia.Controls
var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(element); var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(element);
if (suppressAutoRecycle) if (suppressAutoRecycle)
{ {
virtInfo.AutoRecycleCandidate = false; virtInfo!.AutoRecycleCandidate = false;
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "GetElement: {Index} Not AutoRecycleCandidate:", virtInfo.Index); Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "GetElement: {Index} Not AutoRecycleCandidate:", virtInfo.Index);
} }
else else
{ {
virtInfo.AutoRecycleCandidate = true; virtInfo!.AutoRecycleCandidate = true;
virtInfo.KeepAlive = true; virtInfo.KeepAlive = true;
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "GetElement: {Index} AutoRecycleCandidate:", virtInfo.Index); Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "GetElement: {Index} AutoRecycleCandidate:", virtInfo.Index);
} }

4
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs

@ -110,7 +110,7 @@ namespace Avalonia.Diagnostics.ViewModels
private void UpdateSizeConstraints() private void UpdateSizeConstraints()
{ {
if (_control is IAvaloniaObject ao) if (_control is AvaloniaObject ao)
{ {
string? CreateConstraintInfo(StyledProperty<double> minProperty, StyledProperty<double> maxProperty) string? CreateConstraintInfo(StyledProperty<double> minProperty, StyledProperty<double> maxProperty)
{ {
@ -191,7 +191,7 @@ namespace Avalonia.Diagnostics.ViewModels
} }
else else
{ {
if (_control is IAvaloniaObject ao) if (_control is AvaloniaObject ao)
{ {
if (e.Property == Layoutable.MarginProperty) if (e.Property == Layoutable.MarginProperty)
{ {

2
src/Avalonia.Input/KeyboardNavigation.cs

@ -58,7 +58,7 @@ namespace Avalonia.Input
/// <returns>The <see cref="KeyboardNavigationMode"/> for the container.</returns> /// <returns>The <see cref="KeyboardNavigationMode"/> for the container.</returns>
public static int GetTabIndex(IInputElement element) public static int GetTabIndex(IInputElement element)
{ {
return ((IAvaloniaObject)element).GetValue(TabIndexProperty); return ((AvaloniaObject)element).GetValue(TabIndexProperty);
} }
/// <summary> /// <summary>

5
src/Avalonia.Input/Navigation/TabNavigation.cs

@ -610,7 +610,7 @@ namespace Avalonia.Input.Navigation
private static IInputElement? GetActiveElement(IInputElement e) private static IInputElement? GetActiveElement(IInputElement e)
{ {
return ((IAvaloniaObject)e).GetValue(KeyboardNavigation.TabOnceActiveElementProperty); return ((AvaloniaObject)e).GetValue(KeyboardNavigation.TabOnceActiveElementProperty);
} }
private static IInputElement GetGroupParent(IInputElement e) => GetGroupParent(e, false); private static IInputElement GetGroupParent(IInputElement e) => GetGroupParent(e, false);
@ -655,8 +655,9 @@ namespace Avalonia.Input.Navigation
private static KeyboardNavigationMode GetKeyNavigationMode(IInputElement e) private static KeyboardNavigationMode GetKeyNavigationMode(IInputElement e)
{ {
return ((IAvaloniaObject)e).GetValue(KeyboardNavigation.TabNavigationProperty); return ((AvaloniaObject)e).GetValue(KeyboardNavigation.TabNavigationProperty);
} }
private static bool IsFocusScope(IInputElement e) => FocusManager.GetIsFocusScope(e) || GetParent(e) == null; private static bool IsFocusScope(IInputElement e) => FocusManager.GetIsFocusScope(e) || GetParent(e) == null;
private static bool IsGroup(IInputElement e) => GetKeyNavigationMode(e) != KeyboardNavigationMode.Continue; private static bool IsGroup(IInputElement e) => GetKeyNavigationMode(e) != KeyboardNavigationMode.Continue;

7
src/Avalonia.Styling/StyledElement.cs

@ -467,7 +467,12 @@ namespace Avalonia
/// <param name="parent">The parent.</param> /// <param name="parent">The parent.</param>
void ISetInheritanceParent.SetParent(IAvaloniaObject? parent) void ISetInheritanceParent.SetParent(IAvaloniaObject? parent)
{ {
InheritanceParent = parent; InheritanceParent = parent switch
{
AvaloniaObject ao => ao,
null => null,
_ => throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.")
};
} }
void IStyleable.StyleApplied(IStyleInstance instance) void IStyleable.StyleApplied(IStyleInstance instance)

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -135,7 +135,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
NameScopeSetNameScope = NameScope.GetMethod(new FindMethodMethodSignature("SetNameScope", NameScopeSetNameScope = NameScope.GetMethod(new FindMethodMethodSignature("SetNameScope",
XamlIlTypes.Void, StyledElement, INameScope) XamlIlTypes.Void, StyledElement, INameScope)
{ IsStatic = true }); { IsStatic = true });
AvaloniaObjectSetValueMethod = AvaloniaObject.FindMethod("SetValue", XamlIlTypes.Void, AvaloniaObjectSetValueMethod = AvaloniaObject.FindMethod("SetValue", IDisposable,
false, AvaloniaProperty, XamlIlTypes.Object, BindingPriority); false, AvaloniaProperty, XamlIlTypes.Object, BindingPriority);
IPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.IPropertyInfo"); IPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.IPropertyInfo");
ClrPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.ClrPropertyInfo"); ClrPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.ClrPropertyInfo");

12
tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

@ -152,35 +152,35 @@ namespace Avalonia.Base.UnitTests
} }
internal override IDisposable RouteBind( internal override IDisposable RouteBind(
IAvaloniaObject o, AvaloniaObject o,
IObservable<BindingValue<object>> source, IObservable<BindingValue<object>> source,
BindingPriority priority) BindingPriority priority)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
internal override void RouteClearValue(IAvaloniaObject o) internal override void RouteClearValue(AvaloniaObject o)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
internal override object RouteGetValue(IAvaloniaObject o) internal override object RouteGetValue(AvaloniaObject o)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
internal override object RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority) internal override object RouteGetBaseValue(AvaloniaObject o, BindingPriority maxPriority)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
internal override void RouteInheritanceParentChanged(AvaloniaObject o, IAvaloniaObject oldParent) internal override void RouteInheritanceParentChanged(AvaloniaObject o, AvaloniaObject oldParent)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
internal override IDisposable RouteSetValue( internal override IDisposable RouteSetValue(
IAvaloniaObject o, AvaloniaObject o,
object value, object value,
BindingPriority priority) BindingPriority priority)
{ {

83
tests/Avalonia.Markup.UnitTests/Data/BindingTests_TemplatedParent.cs

@ -1,16 +1,11 @@
using System; using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects;
using Moq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Styling;
using Xunit;
using System.Reactive.Disposables;
using Avalonia.UnitTests;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using System.Linq; using Xunit;
using Avalonia.Markup.Data;
namespace Avalonia.Markup.UnitTests.Data namespace Avalonia.Markup.UnitTests.Data
{ {
@ -19,53 +14,57 @@ namespace Avalonia.Markup.UnitTests.Data
[Fact] [Fact]
public void OneWay_Binding_Should_Be_Set_Up() public void OneWay_Binding_Should_Be_Set_Up()
{ {
var target = CreateTarget(); var source = new Button
var binding = new Binding
{ {
Mode = BindingMode.OneWay, Template = new FuncControlTemplate<Button>((parent, _) =>
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), new ContentPresenter
Priority = BindingPriority.TemplatedParent, {
Path = "Foo", [~ContentPresenter.ContentProperty] = new Binding
{
Mode = BindingMode.OneWay,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Path = "Content",
}
}),
}; };
target.Object.Bind(TextBox.TextProperty, binding); source.ApplyTemplate();
target.Verify(x => x.Bind( var target = (ContentPresenter)source.GetVisualChildren().Single();
TextBox.TextProperty,
It.IsAny<IObservable<BindingValue<string>>>())); Assert.Null(target.Content);
source.Content = "foo";
Assert.Equal("foo", target.Content);
source.Content = "bar";
Assert.Equal("bar", target.Content);
} }
[Fact] [Fact]
public void TwoWay_Binding_Should_Be_Set_Up() public void TwoWay_Binding_Should_Be_Set_Up()
{ {
var target = CreateTarget(); var source = new Button
var binding = new Binding
{ {
Mode = BindingMode.TwoWay, Template = new FuncControlTemplate<Button>((parent, _) =>
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), new ContentPresenter
Priority = BindingPriority.TemplatedParent, {
Path = "Foo", [~ContentPresenter.ContentProperty] = new Binding
{
Mode = BindingMode.TwoWay,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Path = "Content",
}
}),
}; };
target.Object.Bind(TextBox.TextProperty, binding); source.ApplyTemplate();
target.Verify(x => x.Bind(
TextBox.TextProperty,
It.IsAny<IObservable<BindingValue<string>>>()));
}
private Mock<IControl> CreateTarget( var target = (ContentPresenter)source.GetVisualChildren().Single();
ITemplatedControl templatedParent = null,
string text = null)
{
var result = new Mock<IControl>();
result.Setup(x => x.GetValue(Control.TemplatedParentProperty)).Returns(templatedParent); Assert.Null(target.Content);
result.Setup(x => x.GetValue(Control.TemplatedParentProperty)).Returns(templatedParent); source.Content = "foo";
result.Setup(x => x.GetValue(TextBox.TextProperty)).Returns(text); Assert.Equal("foo", target.Content);
result.Setup(x => x.Bind(It.IsAny<DirectPropertyBase<string>>(), It.IsAny<IObservable<BindingValue<string>>>())) target.Content = "bar";
.Returns(Disposable.Empty); Assert.Equal("bar", source.Content);
return result;
} }
} }
} }

49
tests/Avalonia.Styling.UnitTests/SetterTests.cs

@ -5,6 +5,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using Avalonia.Diagnostics;
using Moq; using Moq;
using Xunit; using Xunit;
@ -104,51 +105,51 @@ namespace Avalonia.Styling.UnitTests
[Fact] [Fact]
public void Setter_Should_Apply_Value_With_Activator_As_Binding_With_StyleTrigger_Priority() public void Setter_Should_Apply_Value_With_Activator_As_Binding_With_StyleTrigger_Priority()
{ {
var control = new Mock<IStyleable>(); var control = new Canvas();
var style = Mock.Of<Style>();
var setter = new Setter(TextBlock.TagProperty, "foo"); var setter = new Setter(TextBlock.TagProperty, "foo");
var activator = new Subject<bool>();
var instance = setter.Instance(control.Object); var instance = setter.Instance(control);
instance.Start(true); instance.Start(true);
instance.Activate(); instance.Activate();
control.Verify(x => x.Bind( Assert.Equal("foo", control.Tag);
TextBlock.TagProperty, Assert.Equal(BindingPriority.StyleTrigger, control.GetDiagnostic(TextBlock.TagProperty).Priority);
It.IsAny<IObservable<BindingValue<object>>>(),
BindingPriority.StyleTrigger));
} }
[Fact] [Fact]
public void Setter_Should_Apply_Binding_Without_Activator_With_Style_Priority() public void Setter_Should_Apply_Binding_Without_Activator_With_Style_Priority()
{ {
var control = new Mock<IStyleable>(); var control = new Canvas();
var style = Mock.Of<Style>(); var source = new { Foo = "foo" };
var setter = new Setter(TextBlock.TagProperty, CreateMockBinding(TextBlock.TagProperty)); var setter = new Setter(TextBlock.TagProperty, new Binding
{
Source = source,
Path = nameof(source.Foo),
});
setter.Instance(control.Object).Start(false); setter.Instance(control).Start(false);
control.Verify(x => x.Bind( Assert.Equal("foo", control.Tag);
TextBlock.TagProperty, Assert.Equal(BindingPriority.Style, control.GetDiagnostic(TextBlock.TagProperty).Priority);
It.IsAny<PropertySetterBindingInstance<object>>(),
BindingPriority.Style));
} }
[Fact] [Fact]
public void Setter_Should_Apply_Binding_With_Activator_With_StyleTrigger_Priority() public void Setter_Should_Apply_Binding_With_Activator_With_StyleTrigger_Priority()
{ {
var control = new Mock<IStyleable>(); var control = new Canvas();
var style = Mock.Of<Style>(); var source = new { Foo = "foo" };
var setter = new Setter(TextBlock.TagProperty, CreateMockBinding(TextBlock.TagProperty)); var setter = new Setter(TextBlock.TagProperty, new Binding
{
Source = source,
Path = nameof(source.Foo),
});
var instance = setter.Instance(control.Object); var instance = setter.Instance(control);
instance.Start(true); instance.Start(true);
instance.Activate(); instance.Activate();
control.Verify(x => x.Bind( Assert.Equal("foo", control.Tag);
TextBlock.TagProperty, Assert.Equal(BindingPriority.StyleTrigger, control.GetDiagnostic(TextBlock.TagProperty).Priority);
It.IsAny<IObservable<BindingValue<object>>>(),
BindingPriority.StyleTrigger));
} }
private IBinding CreateMockBinding(AvaloniaProperty property) private IBinding CreateMockBinding(AvaloniaProperty property)

Loading…
Cancel
Save