Browse Source

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

Remove IAvaloniaPropertyVisitor due to generic virtual methods.
pull/8027/head
Steven Kirk 4 years ago
committed by GitHub
parent
commit
4f8dfee9ac
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      src/Avalonia.Base/AvaloniaObject.cs
  2. 11
      src/Avalonia.Base/AvaloniaProperty.cs
  3. 32
      src/Avalonia.Base/DirectPropertyBase.cs
  4. 24
      src/Avalonia.Base/Interactivity/IInteractive.cs
  5. 32
      src/Avalonia.Base/StyledPropertyBase.cs
  6. 65
      src/Avalonia.Base/Styling/Setter.cs
  7. 26
      src/Avalonia.Base/Threading/IDispatcher.cs
  8. 32
      src/Avalonia.Base/Utilities/IAvaloniaPropertyVisitor.cs
  9. 10
      src/Avalonia.Controls/AutoCompleteBox.cs
  10. 9
      src/Avalonia.Controls/Button.cs
  11. 4
      src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
  12. 9
      src/Avalonia.Controls/MenuItem.cs
  13. 10
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  14. 10
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  15. 7
      src/Avalonia.Controls/Slider.cs
  16. 7
      src/Avalonia.Controls/TextBox.cs
  17. 2
      src/Avalonia.Controls/TreeView.cs
  18. 2
      src/Avalonia.Controls/TreeViewItem.cs
  19. 28
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs
  20. 11
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

12
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);
}
}

11
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.

32
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!);
}
}
}
}

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>

32
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));

65
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

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);
}
}

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);
}
}

10
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);
}
}

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

4
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);
}
}

9
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;

10
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);
}
}

10
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);
}
}

7
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);
}
}

7
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);
}
}

2
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,

2
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>(

28
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)));
}
}

11
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

Loading…
Cancel
Save