Browse Source

Merge pull request #8022 from AvaloniaUI/refactor/remove-propertyvisitor

Remove IAvaloniaPropertyVisitor due to generic virtual methods.
Steven Kirk 3 years ago
parent
commit
4f8dfee9ac

+ 7 - 5
src/Avalonia.Base/AvaloniaObject.cs

@@ -646,10 +646,12 @@ namespace Avalonia
         /// enabled.
         /// </summary>
         /// <param name="property">The property.</param>
-        /// <param name="value">The new binding value for the property.</param>
-        protected virtual void UpdateDataValidation<T>(
-            AvaloniaProperty<T> property,
-            BindingValue<T> value)
+        /// <param name="state">The current data binding state.</param>
+        /// <param name="error">The current data binding error, if any.</param>
+        protected virtual void UpdateDataValidation(
+            AvaloniaProperty property,
+            BindingValueType state,
+            Exception? error)
         {
         }
 
@@ -860,7 +862,7 @@ namespace Avalonia
 
             if (metadata.EnableDataValidation == true)
             {
-                UpdateDataValidation(property, value);
+                UpdateDataValidation(property, value.Type, value.Error);
             }
         }
 

+ 2 - 9
src/Avalonia.Base/AvaloniaProperty.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using Avalonia.Data;
 using Avalonia.Data.Core;
+using Avalonia.Styling;
 using Avalonia.Utilities;
 
 namespace Avalonia
@@ -454,15 +455,6 @@ namespace Avalonia
             return Name;
         }
 
-        /// <summary>
-        /// Uses the visitor pattern to resolve an untyped property to a typed property.
-        /// </summary>
-        /// <typeparam name="TData">The type of user data passed.</typeparam>
-        /// <param name="visitor">The visitor which will accept the typed property.</param>
-        /// <param name="data">The user data to pass.</param>
-        public abstract void Accept<TData>(IAvaloniaPropertyVisitor<TData> visitor, ref TData data)
-            where TData : struct;
-
         /// <summary>
         /// Routes an untyped ClearValue call to a typed call.
         /// </summary>
@@ -508,6 +500,7 @@ namespace Avalonia
             BindingPriority priority);
 
         internal abstract void RouteInheritanceParentChanged(AvaloniaObject o, AvaloniaObject? oldParent);
+        internal abstract ISetterInstance CreateSetterInstance(IStyleable target, object? value);
 
         /// <summary>
         /// Overrides the metadata for the property on the specified type.

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

@@ -1,6 +1,7 @@
 using System;
 using Avalonia.Data;
 using Avalonia.Reactive;
+using Avalonia.Styling;
 using Avalonia.Utilities;
 
 namespace Avalonia
@@ -120,12 +121,6 @@ namespace Avalonia
             base.OverrideMetadata(type, metadata);
         }
 
-        /// <inheritdoc/>
-        public override void Accept<TData>(IAvaloniaPropertyVisitor<TData> visitor, ref TData data)
-        {
-            visitor.Visit(this, ref data);
-        }
-
         /// <inheritdoc/>
         internal override void RouteClearValue(AvaloniaObject o)
         {
@@ -181,5 +176,30 @@ namespace Avalonia
         {
             throw new NotSupportedException("Direct properties do not support inheritance.");
         }
+
+        internal override ISetterInstance CreateSetterInstance(IStyleable target, object? value)
+        {
+            if (value is IBinding binding)
+            {
+                return new PropertySetterBindingInstance<TValue>(
+                    target,
+                    this,
+                    binding);
+            }
+            else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType))
+            {
+                return new PropertySetterLazyInstance<TValue>(
+                    target,
+                    this,
+                    () => (TValue)template.Build());
+            }
+            else
+            {
+                return new PropertySetterInstance<TValue>(
+                    target,
+                    this,
+                    (TValue)value!);
+            }
+        }
     }
 }

+ 0 - 24
src/Avalonia.Base/Interactivity/IInteractive.cs

@@ -28,21 +28,6 @@ namespace Avalonia.Interactivity
             RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
             bool handledEventsToo = false);
 
-        /// <summary>
-        /// Adds a handler for the specified routed event.
-        /// </summary>
-        /// <typeparam name="TEventArgs">The type of the event's args.</typeparam>
-        /// <param name="routedEvent">The routed event.</param>
-        /// <param name="handler">The handler.</param>
-        /// <param name="routes">The routing strategies to listen to.</param>
-        /// <param name="handledEventsToo">Whether handled events should also be listened for.</param>
-        /// <returns>A disposable that terminates the event subscription.</returns>
-        void AddHandler<TEventArgs>(
-            RoutedEvent<TEventArgs> routedEvent,
-            EventHandler<TEventArgs> handler,
-            RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
-            bool handledEventsToo = false) where TEventArgs : RoutedEventArgs;
-
         /// <summary>
         /// Removes a handler for the specified routed event.
         /// </summary>
@@ -50,15 +35,6 @@ namespace Avalonia.Interactivity
         /// <param name="handler">The handler.</param>
         void RemoveHandler(RoutedEvent routedEvent, Delegate handler);
 
-        /// <summary>
-        /// Removes a handler for the specified routed event.
-        /// </summary>
-        /// <typeparam name="TEventArgs">The type of the event's args.</typeparam>
-        /// <param name="routedEvent">The routed event.</param>
-        /// <param name="handler">The handler.</param>
-        void RemoveHandler<TEventArgs>(RoutedEvent<TEventArgs> routedEvent, EventHandler<TEventArgs> handler)
-            where TEventArgs : RoutedEventArgs;
-
         /// <summary>
         /// Adds the object's handlers for a routed event to an event route.
         /// </summary>

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

@@ -2,6 +2,7 @@ using System;
 using System.Diagnostics;
 using Avalonia.Data;
 using Avalonia.Reactive;
+using Avalonia.Styling;
 using Avalonia.Utilities;
 
 namespace Avalonia
@@ -158,12 +159,6 @@ namespace Avalonia
             base.OverrideMetadata(type, metadata);
         }
 
-        /// <inheritdoc/>
-        public override void Accept<TData>(IAvaloniaPropertyVisitor<TData> visitor, ref TData data)
-        {
-            visitor.Visit(this, ref data);
-        }
-
         /// <summary>
         /// Gets the string representation of the property.
         /// </summary>
@@ -237,6 +232,31 @@ namespace Avalonia
             o.InheritanceParentChanged(this, oldParent);
         }
 
+        internal override ISetterInstance CreateSetterInstance(IStyleable target, object? value)
+        {
+            if (value is IBinding binding)
+            {
+                return new PropertySetterBindingInstance<TValue>(
+                    target,
+                    this,
+                    binding);
+            }
+            else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType))
+            {
+                return new PropertySetterLazyInstance<TValue>(
+                    target,
+                    this,
+                    () => (TValue)template.Build());
+            }
+            else
+            {
+                return new PropertySetterInstance<TValue>(
+                    target,
+                    this,
+                    (TValue)value!);
+            }
+        }
+
         private object? GetDefaultBoxedValue(Type type)
         {
             _ = type ?? throw new ArgumentNullException(nameof(type));

+ 2 - 63
src/Avalonia.Base/Styling/Setter.cs

@@ -16,7 +16,7 @@ namespace Avalonia.Styling
     /// A <see cref="Setter"/> is used to set a <see cref="AvaloniaProperty"/> value on a
     /// <see cref="AvaloniaObject"/> depending on a condition.
     /// </remarks>
-    public class Setter : ISetter, IAnimationSetter, IAvaloniaPropertyVisitor<Setter.SetterVisitorData>
+    public class Setter : ISetter, IAnimationSetter
     {
         private object? _value;
 
@@ -68,68 +68,7 @@ namespace Avalonia.Styling
                 throw new InvalidOperationException("Setter.Property must be set.");
             }
 
-            var data = new SetterVisitorData
-            {
-                target = target,
-                value = Value,
-            };
-
-            Property.Accept(this, ref data);
-            return data.result!;
-        }
-
-        void IAvaloniaPropertyVisitor<SetterVisitorData>.Visit<T>(
-            StyledPropertyBase<T> property,
-            ref SetterVisitorData data)
-        {
-            if (data.value is IBinding binding)
-            {
-                data.result = new PropertySetterBindingInstance<T>(
-                    data.target,
-                    property,
-                    binding);
-            }
-            else if (data.value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(property.PropertyType))
-            {
-                data.result = new PropertySetterLazyInstance<T>(
-                    data.target,
-                    property,
-                    () => (T)template.Build());
-            }
-            else
-            {
-                data.result = new PropertySetterInstance<T>(
-                    data.target,
-                    property,
-                    (T)data.value!);
-            }
-        }
-
-        void IAvaloniaPropertyVisitor<SetterVisitorData>.Visit<T>(
-            DirectPropertyBase<T> property,
-            ref SetterVisitorData data)
-        {
-            if (data.value is IBinding binding)
-            {
-                data.result = new PropertySetterBindingInstance<T>(
-                    data.target,
-                    property,
-                    binding);
-            }
-            else if (data.value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(property.PropertyType))
-            {
-                data.result = new PropertySetterLazyInstance<T>(
-                    data.target,
-                    property,
-                    () => (T)template.Build());
-            }
-            else
-            {
-                data.result = new PropertySetterInstance<T>(
-                    data.target,
-                    property,
-                    (T)data.value!);
-            }
+            return Property.CreateSetterInstance(target, Value);
         }
 
         private struct SetterVisitorData

+ 0 - 26
src/Avalonia.Base/Threading/IDispatcher.cs

@@ -26,15 +26,6 @@ namespace Avalonia.Threading
         /// <param name="priority">The priority with which to invoke the method.</param>
         void Post(Action action, DispatcherPriority priority = default);
 
-        /// <summary>
-        /// Posts an action that will be invoked on the dispatcher thread.
-        /// </summary>
-        /// <typeparam name="T">type of argument</typeparam>
-        /// <param name="action">The method to call.</param>
-        /// <param name="arg">The argument of method to call.</param>
-        /// <param name="priority">The priority with which to invoke the method.</param>
-        void Post<T>(Action<T> action, T arg, DispatcherPriority priority = default);
-
         /// <summary>
         /// Invokes a action on the dispatcher thread.
         /// </summary>
@@ -43,14 +34,6 @@ namespace Avalonia.Threading
         /// <returns>A task that can be used to track the method's execution.</returns>
         Task InvokeAsync(Action action, DispatcherPriority priority = default);
 
-        /// <summary>
-        /// Invokes a method on the dispatcher thread.
-        /// </summary>
-        /// <param name="function">The method.</param>
-        /// <param name="priority">The priority with which to invoke the method.</param>
-        /// <returns>A task that can be used to track the method's execution.</returns>
-        Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority = default);
-
         /// <summary>
         /// Queues the specified work to run on the dispatcher thread and returns a proxy for the
         /// task returned by <paramref name="function"/>.
@@ -59,14 +42,5 @@ namespace Avalonia.Threading
         /// <param name="priority">The priority with which to invoke the method.</param>
         /// <returns>A task that represents a proxy for the task returned by <paramref name="function"/>.</returns>
         Task InvokeAsync(Func<Task> function, DispatcherPriority priority = default);
-
-        /// <summary>
-        /// Queues the specified work to run on the dispatcher thread and returns a proxy for the
-        /// task returned by <paramref name="function"/>.
-        /// </summary>
-        /// <param name="function">The work to execute asynchronously.</param>
-        /// <param name="priority">The priority with which to invoke the method.</param>
-        /// <returns>A task that represents a proxy for the task returned by <paramref name="function"/>.</returns>
-        Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> function, DispatcherPriority priority = default);
     }
 }

+ 0 - 32
src/Avalonia.Base/Utilities/IAvaloniaPropertyVisitor.cs

@@ -1,32 +0,0 @@
-namespace Avalonia.Utilities
-{
-    /// <summary>
-    /// A visitor to resolve an untyped <see cref="AvaloniaProperty"/> to a typed property.
-    /// </summary>
-    /// <typeparam name="TData">The type of user data passed.</typeparam>
-    /// <remarks>
-    /// Pass an instance that implements this interface to
-    /// <see cref="AvaloniaProperty.Accept{TData}(IAvaloniaPropertyVisitor{TData}, ref TData)"/>
-    /// in order to resolve un untyped <see cref="AvaloniaProperty"/> to a typed
-    /// <see cref="StyledPropertyBase{TValue}"/> or <see cref="DirectPropertyBase{TValue}"/>.
-    /// </remarks>
-    public interface IAvaloniaPropertyVisitor<TData>
-        where TData : struct
-    {
-        /// <summary>
-        /// Called when the property is a styled property.
-        /// </summary>
-        /// <typeparam name="T">The property value type.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="data">The user data.</param>
-        void Visit<T>(StyledPropertyBase<T> property, ref TData data);
-
-        /// <summary>
-        /// Called when the property is a direct property.
-        /// </summary>
-        /// <typeparam name="T">The property value type.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="data">The user data.</param>
-        void Visit<T>(DirectPropertyBase<T> property, ref TData data);
-    }
-}

+ 7 - 3
src/Avalonia.Controls/AutoCompleteBox.cs

@@ -1346,12 +1346,16 @@ namespace Avalonia.Controls
         /// enabled.
         /// </summary>
         /// <param name="property">The property.</param>
-        /// <param name="value">The new binding value for the property.</param>
-        protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
+        /// <param name="state">The current data binding state.</param>
+        /// <param name="error">The current data binding error, if any.</param>
+        protected override void UpdateDataValidation(
+            AvaloniaProperty property,
+            BindingValueType state,
+            Exception? error)
         {
             if (property == TextProperty || property == SelectedItemProperty)
             {
-                DataValidationErrors.SetError(this, value.Error);
+                DataValidationErrors.SetError(this, error);
             }
         }
 

+ 6 - 3
src/Avalonia.Controls/Button.cs

@@ -498,12 +498,15 @@ namespace Avalonia.Controls
         protected override AutomationPeer OnCreateAutomationPeer() => new ButtonAutomationPeer(this);
 
         /// <inheritdoc/>
-        protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
+        protected override void UpdateDataValidation(
+            AvaloniaProperty property,
+            BindingValueType state,
+            Exception? error)
         {
-            base.UpdateDataValidation(property, value);
+            base.UpdateDataValidation(property, state, error);
             if (property == CommandProperty)
             {
-                if (value.Type == BindingValueType.BindingError)
+                if (state == BindingValueType.BindingError)
                 {
                     if (_commandCanExecute)
                     {

+ 2 - 2
src/Avalonia.Controls/Calendar/CalendarDatePicker.cs

@@ -540,11 +540,11 @@ namespace Avalonia.Controls
             }
         }
 
-        protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
+        protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error)
         {
             if (property == SelectedDateProperty)
             {
-                DataValidationErrors.SetError(this, value.Error);
+                DataValidationErrors.SetError(this, error);
             }
         }
 

+ 6 - 3
src/Avalonia.Controls/MenuItem.cs

@@ -501,12 +501,15 @@ namespace Avalonia.Controls
             return new MenuItemAutomationPeer(this);
         }
 
-        protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
+        protected override void UpdateDataValidation(
+            AvaloniaProperty property,
+            BindingValueType state,
+            Exception? error)
         {
-            base.UpdateDataValidation(property, value);
+            base.UpdateDataValidation(property, state, error);
             if (property == CommandProperty)
             {
-                _commandBindingError = value.Type == BindingValueType.BindingError;
+                _commandBindingError = state == BindingValueType.BindingError;
                 if (_commandBindingError && _commandCanExecute)
                 {
                     _commandCanExecute = false;

+ 7 - 3
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@@ -403,12 +403,16 @@ namespace Avalonia.Controls
         /// enabled.
         /// </summary>
         /// <param name="property">The property.</param>
-        /// <param name="value">The new binding value for the property.</param>
-        protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
+        /// <param name="state">The current data binding state.</param>
+        /// <param name="error">The current data binding error, if any.</param>
+        protected override void UpdateDataValidation(
+            AvaloniaProperty property,
+            BindingValueType state,
+            Exception? error)
         {
             if (property == TextProperty || property == ValueProperty)
             {
-                DataValidationErrors.SetError(this, value.Error);
+                DataValidationErrors.SetError(this, error);
             }
         }
 

+ 7 - 3
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@@ -501,12 +501,16 @@ namespace Avalonia.Controls.Primitives
         /// enabled.
         /// </summary>
         /// <param name="property">The property.</param>
-        /// <param name="value">The new binding value for the property.</param>
-        protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
+        /// <param name="state">The current data binding state.</param>
+        /// <param name="error">The current data binding error, if any.</param>
+        protected override void UpdateDataValidation(
+            AvaloniaProperty property,
+            BindingValueType state,
+            Exception? error)
         {
             if (property == SelectedItemProperty)
             {
-                DataValidationErrors.SetError(this, value.Error);
+                DataValidationErrors.SetError(this, error);
             }
         }
         

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

@@ -361,11 +361,14 @@ namespace Avalonia.Controls
             Value = IsSnapToTickEnabled ? SnapToTick(finalValue) : finalValue;
         }
 
-        protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
+        protected override void UpdateDataValidation(
+            AvaloniaProperty property,
+            BindingValueType state,
+            Exception? error)
         {
             if (property == ValueProperty)
             {
-                DataValidationErrors.SetError(this, value.Error);
+                DataValidationErrors.SetError(this, error);
             }
         }
 

+ 5 - 2
src/Avalonia.Controls/TextBox.cs

@@ -1262,11 +1262,14 @@ namespace Avalonia.Controls
             return new TextBoxAutomationPeer(this);
         }
 
-        protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
+        protected override void UpdateDataValidation(
+            AvaloniaProperty property,
+            BindingValueType state,
+            Exception? error)
         {
             if (property == TextProperty)
             {
-                DataValidationErrors.SetError(this, value.Error);
+                DataValidationErrors.SetError(this, error);
             }
         }
 

+ 1 - 1
src/Avalonia.Controls/TreeView.cs

@@ -401,7 +401,7 @@ namespace Avalonia.Controls
         protected virtual ITreeItemContainerGenerator CreateTreeItemContainerGenerator() =>
             CreateTreeItemContainerGenerator<TreeViewItem>();
 
-        protected virtual ITreeItemContainerGenerator CreateTreeItemContainerGenerator<TVItem>() where TVItem: TreeViewItem, new()
+        protected ITreeItemContainerGenerator CreateTreeItemContainerGenerator<TVItem>() where TVItem: TreeViewItem, new()
         {
             return new TreeItemContainerGenerator<TVItem>(
                 this,

+ 1 - 1
src/Avalonia.Controls/TreeViewItem.cs

@@ -96,7 +96,7 @@ namespace Avalonia.Controls
         protected override IItemContainerGenerator CreateItemContainerGenerator() => CreateTreeItemContainerGenerator<TreeViewItem>();
 
         /// <inheritdoc/>
-        protected virtual ITreeItemContainerGenerator CreateTreeItemContainerGenerator<TVItem>()
+        protected ITreeItemContainerGenerator CreateTreeItemContainerGenerator<TVItem>()
             where TVItem: TreeViewItem, new()
         {
             return new TreeItemContainerGenerator<TVItem>(

+ 14 - 14
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs

@@ -52,14 +52,14 @@ namespace Avalonia.Base.UnitTests
             source.OnNext(BindingValue<int>.DataValidationError(new Exception()));
             source.OnNext(7);
 
-            var result = target.Notifications.Cast<BindingValue<int>>().ToList();
+            var result = target.Notifications;
             Assert.Equal(4, result.Count);
-            Assert.Equal(BindingValueType.Value, result[0].Type);
-            Assert.Equal(6, result[0].Value);
-            Assert.Equal(BindingValueType.BindingError, result[1].Type);
-            Assert.Equal(BindingValueType.DataValidationError, result[2].Type);
-            Assert.Equal(BindingValueType.Value, result[3].Type);
-            Assert.Equal(7, result[3].Value);
+            Assert.Equal(BindingValueType.Value, result[0].type);
+            Assert.Equal(6, result[0].value);
+            Assert.Equal(BindingValueType.BindingError, result[1].type);
+            Assert.Equal(BindingValueType.DataValidationError, result[2].type);
+            Assert.Equal(BindingValueType.Value, result[3].type);
+            Assert.Equal(7, result[3].value);
         }
 
         [Fact]
@@ -72,8 +72,7 @@ namespace Avalonia.Base.UnitTests
             target.Bind(Class1.NonValidatedDirectProperty, source);
             source.OnNext(1);
 
-            var result = target.Notifications.Cast<BindingValue<int>>().ToList();
-            Assert.Equal(1, result.Count);
+            Assert.Equal(1, target.Notifications.Count);
         }
 
         [Fact]
@@ -154,13 +153,14 @@ namespace Avalonia.Base.UnitTests
                 set { SetAndRaise(ValidatedDirectStringProperty, ref _directString, value); }
             }
 
-            public IList<object> Notifications { get; } = new List<object>();
+            public List<(BindingValueType type, object value)> Notifications { get; } = new();
 
-            protected override void UpdateDataValidation<T>(
-                AvaloniaProperty<T> property,
-                BindingValue<T> value)
+            protected override void UpdateDataValidation(
+                AvaloniaProperty property,
+                BindingValueType state,
+                Exception error)
             {
-                Notifications.Add(value);
+                Notifications.Add((state, GetValue(property)));
             }
         }
 

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

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using Avalonia.Data;
+using Avalonia.Styling;
 using Avalonia.Utilities;
 using Xunit;
 
@@ -146,11 +147,6 @@ namespace Avalonia.Base.UnitTests
                 OverrideMetadata(typeof(T), metadata);
             }
 
-            public override void Accept<TData>(IAvaloniaPropertyVisitor<TData> vistor, ref TData data)
-            {
-                throw new NotImplementedException();
-            }
-
             internal override IDisposable RouteBind(
                 AvaloniaObject o,
                 IObservable<BindingValue<object>> source,
@@ -186,6 +182,11 @@ namespace Avalonia.Base.UnitTests
             {
                 throw new NotImplementedException();
             }
+
+            internal override ISetterInstance CreateSetterInstance(IStyleable target, object value)
+            {
+                throw new NotImplementedException();
+            }
         }
 
         private class Class1 : AvaloniaObject