Browse Source

Remove generics from IAvaloniaObject.

Steven Kirk 3 years ago
parent
commit
5763a914fe

+ 35 - 18
src/Avalonia.Base/AvaloniaObject.cs

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

+ 43 - 191
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@@ -25,7 +25,7 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Gets an observable for a <see cref="AvaloniaProperty"/>.
+        /// Gets an observable for an <see cref="AvaloniaProperty"/>.
         /// </summary>
         /// <param name="o">The object.</param>
         /// <param name="property">The property.</param>
@@ -44,7 +44,7 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Gets an observable for a <see cref="AvaloniaProperty"/>.
+        /// Gets an observable for an <see cref="AvaloniaProperty"/>.
         /// </summary>
         /// <param name="o">The object.</param>
         /// <typeparam name="T">The property type.</typeparam>
@@ -64,7 +64,7 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Gets an observable for a <see cref="AvaloniaProperty"/>.
+        /// Gets an observable for an <see cref="AvaloniaProperty"/>.
         /// </summary>
         /// <param name="o">The object.</param>
         /// <param name="property">The property.</param>
@@ -85,7 +85,7 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Gets an observable for a <see cref="AvaloniaProperty"/>.
+        /// Gets an observable for an <see cref="AvaloniaProperty"/>.
         /// </summary>
         /// <param name="o">The object.</param>
         /// <typeparam name="T">The property type.</typeparam>
@@ -128,7 +128,7 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Gets a subject for a <see cref="AvaloniaProperty"/>.
+        /// Gets a subject for an <see cref="AvaloniaProperty"/>.
         /// </summary>
         /// <param name="o">The object.</param>
         /// <param name="property">The property.</param>
@@ -150,7 +150,7 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Gets a subject for a <see cref="AvaloniaProperty"/>.
+        /// Gets a subject for an <see cref="AvaloniaProperty"/>.
         /// </summary>
         /// <typeparam name="T">The property type.</typeparam>
         /// <param name="o">The object.</param>
@@ -230,30 +230,7 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// 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<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.
+        /// Binds an <see cref="AvaloniaProperty"/> to an observable.
         /// </summary>
         /// <typeparam name="T">The type of the property.</typeparam>
         /// <param name="target">The object.</param>
@@ -273,42 +250,22 @@ namespace Avalonia
             property = property ?? throw new ArgumentNullException(nameof(property));
             source = source ?? throw new ArgumentNullException(nameof(source));
 
-            return property switch
+            if (target is AvaloniaObject ao)
             {
-                StyledPropertyBase<T> styled => target.Bind(styled, source, priority),
-                DirectPropertyBase<T> direct => target.Bind(direct, source),
-                _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
-            };
-        }
+                return property switch
+                {
+                    StyledPropertyBase<T> styled => ao.Bind(styled, source, priority),
+                    DirectPropertyBase<T> direct => ao.Bind(direct, source),
+                    _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
+                };
+            }
 
-        /// <summary>
-        /// 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));
+            throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
 
-            return target.Bind(
-                property,
-                source.ToBindingValue(),
-                priority);
         }
 
         /// <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>
@@ -334,7 +291,7 @@ namespace Avalonia
         }
 
         /// <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>
         /// <param name="target">The object.</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>
         /// Gets a <see cref="AvaloniaProperty"/> value.
         /// </summary>
@@ -436,12 +343,18 @@ namespace Avalonia
             target = target ?? throw new ArgumentNullException(nameof(target));
             property = property ?? throw new ArgumentNullException(nameof(property));
 
-            return property switch
+            if (target is AvaloniaObject ao)
             {
-                StyledPropertyBase<T> styled => target.GetValue(styled),
-                DirectPropertyBase<T> direct => target.GetValue(direct),
-                _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
-            };
+                return property switch
+                {
+                    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>
@@ -456,7 +369,7 @@ namespace Avalonia
         /// <see cref="AvaloniaProperty.UnsetValue"/>. Note that this method does not return
         /// 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>
         public static object? GetBaseValue(
             this IAvaloniaObject target,
@@ -466,7 +379,9 @@ namespace Avalonia
             target = target ?? throw new ArgumentNullException(nameof(target));
             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>
@@ -481,8 +396,7 @@ namespace Avalonia
         /// <see cref="Optional{T}.Empty"/>. Note that this method does not return property values
         /// that come from inherited or default values.
         /// 
-        /// For direct properties returns
-        /// <see cref="IAvaloniaObject.GetValue{T}(DirectPropertyBase{T})"/>.
+        /// For direct properties returns the current value of the property.
         /// </remarks>
         public static Optional<T> GetBaseValue<T>(
             this IAvaloniaObject target,
@@ -492,69 +406,18 @@ namespace Avalonia
             target = target ?? throw new ArgumentNullException(nameof(target));
             property = property ?? throw new ArgumentNullException(nameof(property));
 
-            target = target ?? throw new ArgumentNullException(nameof(target));
-            property = property ?? throw new ArgumentNullException(nameof(property));
-
-            return property switch
+            if (target is AvaloniaObject ao)
             {
-                StyledPropertyBase<T> styled => target.GetBaseValue(styled, maxPriority),
-                DirectPropertyBase<T> direct => target.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));
+                return property switch
+                {
+                    StyledPropertyBase<T> styled => ao.GetBaseValue(styled, maxPriority),
+                    DirectPropertyBase<T> direct => ao.GetValue(direct),
+                    _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
+                };
 
-            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>
@@ -622,17 +485,6 @@ namespace Avalonia
             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>
         /// Observer method for <see cref="AddClassHandler{TTarget}(IObservable{AvaloniaPropertyChangedEventArgs},
         /// Func{TTarget, Action{AvaloniaPropertyChangedEventArgs}})"/>.

+ 6 - 6
src/Avalonia.Base/AvaloniaProperty.cs

@@ -467,20 +467,20 @@ namespace Avalonia
         /// Routes an untyped ClearValue call to a typed call.
         /// </summary>
         /// <param name="o">The object instance.</param>
-        internal abstract void RouteClearValue(IAvaloniaObject o);
+        internal abstract void RouteClearValue(AvaloniaObject o);
 
         /// <summary>
         /// Routes an untyped GetValue call to a typed call.
         /// </summary>
         /// <param name="o">The object instance.</param>
-        internal abstract object? RouteGetValue(IAvaloniaObject o);
+        internal abstract object? RouteGetValue(AvaloniaObject o);
 
         /// <summary>
         /// Routes an untyped GetBaseValue call to a typed call.
         /// </summary>
         /// <param name="o">The object instance.</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>
         /// 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.
         /// </returns>
         internal abstract IDisposable? RouteSetValue(
-            IAvaloniaObject o,
+            AvaloniaObject o,
             object? value,
             BindingPriority priority);
 
@@ -503,11 +503,11 @@ namespace Avalonia
         /// <param name="source">The binding source.</param>
         /// <param name="priority">The priority.</param>
         internal abstract IDisposable RouteBind(
-            IAvaloniaObject o,
+            AvaloniaObject o,
             IObservable<BindingValue<object?>> source,
             BindingPriority priority);
 
-        internal abstract void RouteInheritanceParentChanged(AvaloniaObject o, IAvaloniaObject? oldParent);
+        internal abstract void RouteInheritanceParentChanged(AvaloniaObject o, AvaloniaObject? oldParent);
 
         /// <summary>
         /// Overrides the metadata for the property on the specified type.

+ 6 - 6
src/Avalonia.Base/DirectPropertyBase.cs

@@ -127,25 +127,25 @@ namespace Avalonia
         }
 
         /// <inheritdoc/>
-        internal override void RouteClearValue(IAvaloniaObject o)
+        internal override void RouteClearValue(AvaloniaObject o)
         {
             o.ClearValue<TValue>(this);
         }
 
         /// <inheritdoc/>
-        internal override object? RouteGetValue(IAvaloniaObject o)
+        internal override object? RouteGetValue(AvaloniaObject o)
         {
             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);
         }
 
         /// <inheritdoc/>
         internal override IDisposable? RouteSetValue(
-            IAvaloniaObject o,
+            AvaloniaObject o,
             object? value,
             BindingPriority priority)
         {
@@ -169,7 +169,7 @@ namespace Avalonia
 
         /// <inheritdoc/>
         internal override IDisposable RouteBind(
-            IAvaloniaObject o,
+            AvaloniaObject o,
             IObservable<BindingValue<object?>> source,
             BindingPriority priority)
         {
@@ -177,7 +177,7 @@ namespace Avalonia
             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.");
         }

+ 9 - 95
src/Avalonia.Base/IAvaloniaObject.cs

@@ -17,42 +17,14 @@ namespace Avalonia
         /// Clears an <see cref="AvaloniaProperty"/>'s local value.
         /// </summary>
         /// <param name="property">The property.</param>
-        void ClearValue<T>(StyledPropertyBase<T> 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);
+        void ClearValue(AvaloniaProperty 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>(DirectPropertyBase<T> 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);
+        object? GetValue(AvaloniaProperty property);
 
         /// <summary>
         /// Checks whether a <see cref="AvaloniaProperty"/> is animating.
@@ -71,93 +43,35 @@ namespace Avalonia
         /// <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>
         /// <param name="priority">The priority of the value.</param>
         /// <returns>
         /// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
         /// </returns>
-        IDisposable? SetValue<T>(
-            StyledPropertyBase<T> property,
-            T value,
+        IDisposable? SetValue(
+            AvaloniaProperty property,
+            object? value,
             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>
         /// 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>
         /// <param name="priority">The priority of the binding.</param>
         /// <returns>
         /// A disposable which can be used to terminate the binding.
         /// </returns>
-        IDisposable Bind<T>(
-            StyledPropertyBase<T> property,
-            IObservable<BindingValue<T>> source,
+        IDisposable Bind(
+            AvaloniaProperty property,
+            IObservable<object?> source,
             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>
         /// Coerces the specified <see cref="AvaloniaProperty"/>.
         /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
         /// <param name="property">The property.</param>
-        void CoerceValue<T>(StyledPropertyBase<T> 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);
+        void CoerceValue(AvaloniaProperty property);
     }
 }

+ 9 - 2
src/Avalonia.Base/PropertyStore/PriorityValue.cs

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

+ 1 - 1
src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs

@@ -51,7 +51,7 @@ namespace Avalonia.Reactive
             {
                 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))
                     {

+ 19 - 11
src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs

@@ -49,23 +49,31 @@ namespace Avalonia.Reactive
         {
             if (e.Property == _property)
             {
-                T newValue;
-
-                if (e is AvaloniaPropertyChangedEventArgs<T> typed)
+                if (e.Sender is AvaloniaObject ao)
                 {
-                    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
                 {
-                    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!);
-                }
             }
         }
     }

+ 6 - 6
src/Avalonia.Base/StyledPropertyBase.cs

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

+ 2 - 2
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 (slot is PriorityValue<T> p)
+                if (slot is IPriorityValue p)
                 {
                     p.UpdateEffectiveValue();
                 }

+ 26 - 20
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@@ -69,15 +69,18 @@ namespace Avalonia.Controls.Primitives
         {
             foreach (var child in Children)
             {
-                var info = child.GetValue(s_adornedElementInfoProperty);
-
-                if (info != null && info.Bounds.HasValue)
-                {
-                    child.Measure(info.Bounds.Value.Bounds.Size);
-                }
-                else
+                if (child is AvaloniaObject ao)
                 {
-                    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)
             {
-                var info = child.GetValue(s_adornedElementInfoProperty);
-                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
+                if (child is AvaloniaObject ao)
                 {
-                    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));
+                    }
                 }
             }
 

+ 2 - 2
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@@ -533,9 +533,9 @@ namespace Avalonia.Controls.Primitives
 
                 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)
                         {

+ 14 - 10
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@@ -257,10 +257,9 @@ namespace Avalonia.Controls
 
         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 value;
+            return (element as AvaloniaObject)?.GetValue(VirtualizationInfoProperty);
         }
 
         internal static VirtualizationInfo CreateAndInitializeVirtualizationInfo(IControl element)
@@ -277,15 +276,20 @@ namespace Avalonia.Controls
 
         internal static VirtualizationInfo GetVirtualizationInfo(IControl element)
         {
-            var result = element.GetValue(VirtualizationInfoProperty);
-
-            if (result == null)
+            if (element is AvaloniaObject ao)
             {
-                result = new VirtualizationInfo();
-                element.SetValue(VirtualizationInfoProperty, result);
+                var result = ao.GetValue(VirtualizationInfoProperty);
+
+                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()
@@ -491,7 +495,7 @@ namespace Avalonia.Controls
             if (parent == this)
             {
                 var virtInfo = TryGetVirtualizationInfo(element);
-                return _viewManager.GetElementIndex(virtInfo);
+                return _viewManager.GetElementIndex(virtInfo!);
             }
 
             return -1;

+ 2 - 2
src/Avalonia.Controls/Repeater/RecyclePool.cs

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

+ 3 - 3
src/Avalonia.Controls/Repeater/ViewManager.cs

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

+ 2 - 2
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs

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

+ 1 - 1
src/Avalonia.Input/KeyboardNavigation.cs

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

+ 3 - 2
src/Avalonia.Input/Navigation/TabNavigation.cs

@@ -610,7 +610,7 @@ namespace Avalonia.Input.Navigation
 
         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);
@@ -655,8 +655,9 @@ namespace Avalonia.Input.Navigation
 
         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 IsGroup(IInputElement e) => GetKeyNavigationMode(e) != KeyboardNavigationMode.Continue;
 

+ 6 - 1
src/Avalonia.Styling/StyledElement.cs

@@ -467,7 +467,12 @@ namespace Avalonia
         /// <param name="parent">The parent.</param>
         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)

+ 1 - 1
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",
                 XamlIlTypes.Void, StyledElement, INameScope)
             { IsStatic = true });
-            AvaloniaObjectSetValueMethod = AvaloniaObject.FindMethod("SetValue", XamlIlTypes.Void,
+            AvaloniaObjectSetValueMethod = AvaloniaObject.FindMethod("SetValue", IDisposable,
                 false, AvaloniaProperty, XamlIlTypes.Object, BindingPriority);
             IPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.IPropertyInfo");
             ClrPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.ClrPropertyInfo");

+ 6 - 6
tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

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

+ 41 - 42
tests/Avalonia.Markup.UnitTests/Data/BindingTests_TemplatedParent.cs

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

+ 25 - 24
tests/Avalonia.Styling.UnitTests/SetterTests.cs

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