Browse Source

Merge branch 'master' into master

pull/8026/head
kivarsen 4 years ago
committed by GitHub
parent
commit
df0fbc9bac
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
  2. 10
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  3. 13
      samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
  4. 5
      samples/SampleControls/HamburgerMenu/HamburgerMenu.cs
  5. 7
      src/Avalonia.Base/Animation/Animatable.cs
  6. 77
      src/Avalonia.Base/AvaloniaObject.cs
  7. 234
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  8. 23
      src/Avalonia.Base/AvaloniaProperty.cs
  9. 43
      src/Avalonia.Base/AvaloniaPropertyChangedExtensions.cs
  10. 44
      src/Avalonia.Base/DirectPropertyBase.cs
  11. 2
      src/Avalonia.Base/GeometryGroup.cs
  12. 104
      src/Avalonia.Base/IAvaloniaObject.cs
  13. 8
      src/Avalonia.Base/Input/InputElement.cs
  14. 2
      src/Avalonia.Base/Input/KeyboardNavigation.cs
  15. 5
      src/Avalonia.Base/Input/Navigation/TabNavigation.cs
  16. 24
      src/Avalonia.Base/Interactivity/IInteractive.cs
  17. 4
      src/Avalonia.Base/Layout/StackLayout.cs
  18. 18
      src/Avalonia.Base/Layout/UniformGridLayout.cs
  19. 2
      src/Avalonia.Base/Layout/WrapLayout/WrapLayout.cs
  20. 5
      src/Avalonia.Base/Media/DashStyle.cs
  21. 2
      src/Avalonia.Base/Media/DrawingImage.cs
  22. 28
      src/Avalonia.Base/Media/Pen.cs
  23. 17
      src/Avalonia.Base/PropertyStore/BindingEntry.cs
  24. 13
      src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs
  25. 2
      src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs
  26. 3
      src/Avalonia.Base/PropertyStore/IValue.cs
  27. 17
      src/Avalonia.Base/PropertyStore/IValueSink.cs
  28. 5
      src/Avalonia.Base/PropertyStore/LocalValueEntry.cs
  29. 67
      src/Avalonia.Base/PropertyStore/PriorityValue.cs
  30. 45
      src/Avalonia.Base/PropertyStore/ValueOwner.cs
  31. 2
      src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs
  32. 30
      src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs
  33. 7
      src/Avalonia.Base/StyledElement.cs
  34. 44
      src/Avalonia.Base/StyledPropertyBase.cs
  35. 65
      src/Avalonia.Base/Styling/Setter.cs
  36. 26
      src/Avalonia.Base/Threading/IDispatcher.cs
  37. 32
      src/Avalonia.Base/Utilities/IAvaloniaPropertyVisitor.cs
  38. 29
      src/Avalonia.Base/Utilities/IWeakEventSubscriber.cs
  39. 138
      src/Avalonia.Base/Utilities/WeakEvent.cs
  40. 238
      src/Avalonia.Base/Utilities/WeakHashList.cs
  41. 27
      src/Avalonia.Base/ValueStore.cs
  42. 21
      src/Avalonia.Base/Visual.cs
  43. 5
      src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt
  44. 2
      src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs
  45. 4
      src/Avalonia.Controls.DataGrid/DataGridColumn.cs
  46. 2
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  47. 2
      src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs
  48. 17
      src/Avalonia.Controls/ApiCompatBaseline.txt
  49. 10
      src/Avalonia.Controls/AutoCompleteBox.cs
  50. 23
      src/Avalonia.Controls/Button.cs
  51. 4
      src/Avalonia.Controls/ButtonSpinner.cs
  52. 4
      src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
  53. 4
      src/Avalonia.Controls/ContextMenu.cs
  54. 2
      src/Avalonia.Controls/Control.cs
  55. 2
      src/Avalonia.Controls/Documents/Inline.cs
  56. 2
      src/Avalonia.Controls/Documents/Run.cs
  57. 2
      src/Avalonia.Controls/Documents/TextElement.cs
  58. 4
      src/Avalonia.Controls/Expander.cs
  59. 4
      src/Avalonia.Controls/ItemsControl.cs
  60. 2
      src/Avalonia.Controls/MaskedTextBox.cs
  61. 9
      src/Avalonia.Controls/MenuItem.cs
  62. 4
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  63. 10
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  64. 2
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  65. 2
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  66. 2
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  67. 46
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  68. 4
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  69. 20
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  70. 4
      src/Avalonia.Controls/Primitives/Track.cs
  71. 6
      src/Avalonia.Controls/ProgressBar.cs
  72. 4
      src/Avalonia.Controls/RepeatButton.cs
  73. 81
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  74. 4
      src/Avalonia.Controls/Repeater/RecyclePool.cs
  75. 6
      src/Avalonia.Controls/Repeater/ViewManager.cs
  76. 29
      src/Avalonia.Controls/Repeater/ViewportManager.cs
  77. 11
      src/Avalonia.Controls/Slider.cs
  78. 11
      src/Avalonia.Controls/SplitButton/SplitButton.cs
  79. 2
      src/Avalonia.Controls/SplitButton/ToggleSplitButton.cs
  80. 2
      src/Avalonia.Controls/TextBlock.cs
  81. 11
      src/Avalonia.Controls/TextBox.cs
  82. 17
      src/Avalonia.Controls/TopLevel.cs
  83. 2
      src/Avalonia.Controls/TransitioningContentControl.cs
  84. 8
      src/Avalonia.Controls/TrayIcon.cs
  85. 2
      src/Avalonia.Controls/TreeView.cs
  86. 2
      src/Avalonia.Controls/TreeViewItem.cs
  87. 4
      src/Avalonia.Controls/Viewbox.cs
  88. 6
      src/Avalonia.Controls/Window.cs
  89. 4
      src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs
  90. 4
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs
  91. 2
      src/Avalonia.Themes.Default/SimpleTheme.cs
  92. 2
      src/Avalonia.Themes.Fluent/FluentTheme.cs
  93. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  94. 2
      src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs
  95. 28
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs
  96. 15
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs
  97. 23
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
  98. 49
      tests/Avalonia.Base.UnitTests/PriorityValueTests.cs
  99. 49
      tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
  100. 45
      tests/Avalonia.Benchmarks/Visuals/VisualAffectsRenderBenchmarks.cs

2
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml

@ -53,10 +53,12 @@
<ComboBoxItem>UniformGrid - Horizontal</ComboBoxItem>
</ComboBox>
<Button Command="{Binding AddItem}">Add Item</Button>
<Button Command="{Binding RemoveItem}">Remove Item</Button>
<Button Command="{Binding RandomizeHeights}">Randomize Heights</Button>
<Button Command="{Binding ResetItems}">Reset items</Button>
<Button x:Name="scrollToLast">Scroll to Last</Button>
<Button x:Name="scrollToRandom">Scroll to Random</Button>
<Button x:Name="scrollToSelected">Scroll to Selected</Button>
</StackPanel>
<Border BorderThickness="1" BorderBrush="{DynamicResource SystemControlHighlightBaseMediumLowBrush}" Margin="0 0 0 16">
<ScrollViewer Name="scroller"

10
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs

@ -15,8 +15,10 @@ namespace ControlCatalog.Pages
private readonly ItemsRepeaterPageViewModel _viewModel;
private ItemsRepeater _repeater;
private ScrollViewer _scroller;
private int _selectedIndex;
private Button _scrollToLast;
private Button _scrollToRandom;
private Button _scrollToSelected;
private Random _random = new Random(0);
public ItemsRepeaterPage()
@ -26,10 +28,12 @@ namespace ControlCatalog.Pages
_scroller = this.FindControl<ScrollViewer>("scroller");
_scrollToLast = this.FindControl<Button>("scrollToLast");
_scrollToRandom = this.FindControl<Button>("scrollToRandom");
_scrollToSelected = this.FindControl<Button>("scrollToSelected");
_repeater.PointerPressed += RepeaterClick;
_repeater.KeyDown += RepeaterOnKeyDown;
_scrollToLast.Click += scrollToLast_Click;
_scrollToRandom.Click += scrollToRandom_Click;
_scrollToSelected.Click += scrollToSelected_Click;
DataContext = _viewModel = new ItemsRepeaterPageViewModel();
}
@ -121,6 +125,7 @@ namespace ControlCatalog.Pages
{
var item = (e.Source as TextBlock)?.DataContext as ItemsRepeaterPageViewModel.Item;
_viewModel.SelectedItem = item;
_selectedIndex = _viewModel.Items.IndexOf(item);
}
private void RepeaterOnKeyDown(object sender, KeyEventArgs e)
@ -140,5 +145,10 @@ namespace ControlCatalog.Pages
{
ScrollTo(_random.Next(_viewModel.Items.Count - 1));
}
private void scrollToSelected_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
ScrollTo(_selectedIndex);
}
}
}

13
samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs

@ -31,6 +31,19 @@ namespace ControlCatalog.ViewModels
Items.Insert(index + 1, new Item(index + 1) { Text = $"New Item {_newItemIndex++}" });
}
public void RemoveItem()
{
if (SelectedItem is not null)
{
Items.Remove(SelectedItem);
SelectedItem = null;
}
else if (Items.Count > 0)
{
Items.RemoveAt(Items.Count - 1);
}
}
public void RandomizeHeights()
{
var random = new Random();

5
samples/SampleControls/HamburgerMenu/HamburgerMenu.cs

@ -43,14 +43,13 @@ namespace ControlSamples
_splitView = e.NameScope.Find<SplitView>("PART_NavigationPane");
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == BoundsProperty && _splitView is not null)
{
var oldBounds = change.OldValue.GetValueOrDefault<Rect>();
var newBounds = change.NewValue.GetValueOrDefault<Rect>();
var (oldBounds, newBounds) = change.GetOldAndNewValue<Rect>();
EnsureSplitViewMode(oldBounds, newBounds);
}
}

7
src/Avalonia.Base/Animation/Animatable.cs

@ -87,12 +87,13 @@ namespace Avalonia.Animation
}
}
protected sealed override void OnPropertyChangedCore<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected sealed override void OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change)
{
if (change.Property == TransitionsProperty && change.IsEffectiveValueChange)
{
var oldTransitions = change.OldValue.GetValueOrDefault<Transitions>();
var newTransitions = change.NewValue.GetValueOrDefault<Transitions>();
var e = (AvaloniaPropertyChangedEventArgs<Transitions?>)change;
var oldTransitions = e.OldValue.GetValueOrDefault();
var newTransitions = e.NewValue.GetValueOrDefault();
// When transitions are replaced, we add the new transitions before removing the old
// transitions, so that when the old transition being disposed causes the value to

77
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
@ -15,13 +16,13 @@ namespace Avalonia
/// <remarks>
/// This class is analogous to DependencyObject in WPF.
/// </remarks>
public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged, IValueSink
public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged
{
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)
@ -504,7 +524,7 @@ namespace Avalonia
return _propertyChanged?.GetInvocationList();
}
void IValueSink.ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
internal void ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
var property = (StyledPropertyBase<T>)change.Property;
@ -543,7 +563,7 @@ namespace Avalonia
}
}
void IValueSink.Completed<T>(
internal void Completed<T>(
StyledPropertyBase<T> property,
IPriorityValueEntry entry,
Optional<T> oldValue)
@ -554,7 +574,7 @@ namespace Avalonia
oldValue,
default,
BindingPriority.Unset);
((IValueSink)this).ValueChanged(change);
ValueChanged(change);
}
/// <summary>
@ -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);
@ -629,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)
{
}
@ -640,7 +659,7 @@ namespace Avalonia
/// Called when a avalonia property changes on the object.
/// </summary>
/// <param name="change">The property change details.</param>
protected virtual void OnPropertyChangedCore<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected virtual void OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change)
{
if (change.IsEffectiveValueChange)
{
@ -652,7 +671,7 @@ namespace Avalonia
/// Called when a avalonia property changes on the object.
/// </summary>
/// <param name="change">The property change details.</param>
protected virtual void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected virtual void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
}
@ -843,7 +862,7 @@ namespace Avalonia
if (metadata.EnableDataValidation == true)
{
UpdateDataValidation(property, value);
UpdateDataValidation(property, value.Type, value.Error);
}
}

234
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}})"/>.

23
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,33 +455,24 @@ 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>
/// <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 +484,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 +495,12 @@ 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);
internal abstract ISetterInstance CreateSetterInstance(IStyleable target, object? value);
/// <summary>
/// Overrides the metadata for the property on the specified type.

43
src/Avalonia.Base/AvaloniaPropertyChangedExtensions.cs

@ -0,0 +1,43 @@
namespace Avalonia
{
/// <summary>
/// Provides extensions for <see cref="AvaloniaPropertyChangedEventArgs"/>.
/// </summary>
public static class AvaloniaPropertyChangedExtensions
{
/// <summary>
/// Gets a typed value from <see cref="AvaloniaPropertyChangedEventArgs.OldValue"/>.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
/// <param name="e">The event args.</param>
/// <returns>The value.</returns>
public static T GetOldValue<T>(this AvaloniaPropertyChangedEventArgs e)
{
return ((AvaloniaPropertyChangedEventArgs<T>)e).OldValue.GetValueOrDefault()!;
}
/// <summary>
/// Gets a typed value from <see cref="AvaloniaPropertyChangedEventArgs.NewValue"/>.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
/// <param name="e">The event args.</param>
/// <returns>The value.</returns>
public static T GetNewValue<T>(this AvaloniaPropertyChangedEventArgs e)
{
return ((AvaloniaPropertyChangedEventArgs<T>)e).NewValue.GetValueOrDefault()!;
}
/// <summary>
/// Gets a typed value from <see cref="AvaloniaPropertyChangedEventArgs.OldValue"/> and
/// <see cref="AvaloniaPropertyChangedEventArgs.NewValue"/>.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
/// <param name="e">The event args.</param>
/// <returns>The value.</returns>
public static (T oldValue, T newValue) GetOldAndNewValue<T>(this AvaloniaPropertyChangedEventArgs e)
{
var ev = (AvaloniaPropertyChangedEventArgs<T>)e;
return (ev.OldValue.GetValueOrDefault()!, ev.NewValue.GetValueOrDefault()!);
}
}
}

44
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
@ -121,31 +122,25 @@ namespace Avalonia
}
/// <inheritdoc/>
public override void Accept<TData>(IAvaloniaPropertyVisitor<TData> visitor, ref TData data)
{
visitor.Visit(this, ref data);
}
/// <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 +164,7 @@ namespace Avalonia
/// <inheritdoc/>
internal override IDisposable RouteBind(
IAvaloniaObject o,
AvaloniaObject o,
IObservable<BindingValue<object?>> source,
BindingPriority priority)
{
@ -177,9 +172,34 @@ 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.");
}
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!);
}
}
}
}

2
src/Avalonia.Base/GeometryGroup.cs

@ -68,7 +68,7 @@ namespace Avalonia.Media
return null;
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

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

8
src/Avalonia.Base/Input/InputElement.cs

@ -601,21 +601,21 @@ namespace Avalonia.Input
{
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == IsFocusedProperty)
{
UpdatePseudoClasses(change.NewValue.GetValueOrDefault<bool>(), null);
UpdatePseudoClasses(change.GetNewValue<bool>(), null);
}
else if (change.Property == IsPointerOverProperty)
{
UpdatePseudoClasses(null, change.NewValue.GetValueOrDefault<bool>());
UpdatePseudoClasses(null, change.GetNewValue<bool>());
}
else if (change.Property == IsKeyboardFocusWithinProperty)
{
PseudoClasses.Set(":focus-within", change.NewValue.GetValueOrDefault<bool>());
PseudoClasses.Set(":focus-within", change.GetNewValue<bool>());
}
}

2
src/Avalonia.Base/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>

5
src/Avalonia.Base/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;

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>

4
src/Avalonia.Base/Layout/StackLayout.cs

@ -320,11 +320,11 @@ namespace Avalonia.Layout
InvalidateLayout();
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (change.Property == OrientationProperty)
{
var orientation = change.NewValue.GetValueOrDefault<Orientation>();
var orientation = change.GetNewValue<Orientation>();
//Note: For StackLayout Vertical Orientation means we have a Vertical ScrollOrientation.
//Horizontal Orientation means we have a Horizontal ScrollOrientation.

18
src/Avalonia.Base/Layout/UniformGridLayout.cs

@ -471,11 +471,11 @@ namespace Avalonia.Layout
gridState.ClearElementOnDataSourceChange(context, args);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (change.Property == OrientationProperty)
{
var orientation = change.NewValue.GetValueOrDefault<Orientation>();
var orientation = change.GetNewValue<Orientation>();
//Note: For UniformGridLayout Vertical Orientation means we have a Horizontal ScrollOrientation. Horizontal Orientation means we have a Vertical ScrollOrientation.
//i.e. the properties are the inverse of each other.
@ -484,31 +484,31 @@ namespace Avalonia.Layout
}
else if (change.Property == MinColumnSpacingProperty)
{
_minColumnSpacing = change.NewValue.GetValueOrDefault<double>();
_minColumnSpacing = change.GetNewValue<double>();
}
else if (change.Property == MinRowSpacingProperty)
{
_minRowSpacing = change.NewValue.GetValueOrDefault<double>();
_minRowSpacing = change.GetNewValue<double>();
}
else if (change.Property == ItemsJustificationProperty)
{
_itemsJustification = change.NewValue.GetValueOrDefault<UniformGridLayoutItemsJustification>();
_itemsJustification = change.GetNewValue<UniformGridLayoutItemsJustification>();
}
else if (change.Property == ItemsStretchProperty)
{
_itemsStretch = change.NewValue.GetValueOrDefault<UniformGridLayoutItemsStretch>();
_itemsStretch = change.GetNewValue<UniformGridLayoutItemsStretch>();
}
else if (change.Property == MinItemWidthProperty)
{
_minItemWidth = change.NewValue.GetValueOrDefault<double>();
_minItemWidth = change.GetNewValue<double>();
}
else if (change.Property == MinItemHeightProperty)
{
_minItemHeight = change.NewValue.GetValueOrDefault<double>();
_minItemHeight = change.GetNewValue<double>();
}
else if (change.Property == MaximumRowsOrColumnsProperty)
{
_maximumRowsOrColumns = change.NewValue.GetValueOrDefault<int>();
_maximumRowsOrColumns = change.GetNewValue<int>();
}
InvalidateLayout();

2
src/Avalonia.Base/Layout/WrapLayout/WrapLayout.cs

@ -322,7 +322,7 @@ namespace Avalonia.Layout
return finalSize;
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

5
src/Avalonia.Base/Media/DashStyle.cs

@ -112,14 +112,13 @@ namespace Avalonia.Media
/// <returns></returns>
public ImmutableDashStyle ToImmutable() => new ImmutableDashStyle(Dashes, Offset);
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == DashesProperty)
{
var oldValue = change.OldValue.GetValueOrDefault<AvaloniaList<double>>();
var newValue = change.NewValue.GetValueOrDefault<AvaloniaList<double>>();
var (oldValue, newValue) = change.GetOldAndNewValue<AvaloniaList<double>>();
if (oldValue is object)
{

2
src/Avalonia.Base/Media/DrawingImage.cs

@ -69,7 +69,7 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

28
src/Avalonia.Base/Media/Pen.cs

@ -7,7 +7,7 @@ namespace Avalonia.Media
/// <summary>
/// Describes how a stroke is drawn.
/// </summary>
public sealed class Pen : AvaloniaObject, IPen, IWeakEventSubscriber<EventArgs>
public sealed class Pen : AvaloniaObject, IPen
{
/// <summary>
/// Defines the <see cref="Brush"/> property.
@ -48,7 +48,8 @@ namespace Avalonia.Media
private EventHandler? _invalidated;
private IAffectsRender? _subscribedToBrush;
private IAffectsRender? _subscribedToDashes;
private TargetWeakEventSubscriber<Pen, EventArgs>? _weakSubscriber;
/// <summary>
/// Initializes a new instance of the <see cref="Pen"/> class.
/// </summary>
@ -192,7 +193,7 @@ namespace Avalonia.Media
MiterLimit);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
_invalidated?.Invoke(this, EventArgs.Empty);
if(change.Property == BrushProperty)
@ -207,13 +208,24 @@ namespace Avalonia.Media
{
if ((_invalidated == null || field != value) && field != null)
{
InvalidatedWeakEvent.Unsubscribe(field, this);
if (_weakSubscriber != null)
InvalidatedWeakEvent.Unsubscribe(field, _weakSubscriber);
field = null;
}
if (_invalidated != null && field != value && value is IAffectsRender affectsRender)
{
InvalidatedWeakEvent.Subscribe(affectsRender, this);
if (_weakSubscriber == null)
{
_weakSubscriber = new TargetWeakEventSubscriber<Pen, EventArgs>(
this, static (target, _, ev, _) =>
{
if (ev == InvalidatedWeakEvent)
target._invalidated?.Invoke(target, EventArgs.Empty);
});
}
InvalidatedWeakEvent.Subscribe(affectsRender, _weakSubscriber);
field = affectsRender;
}
}
@ -223,11 +235,5 @@ namespace Avalonia.Media
UpdateSubscription(ref _subscribedToBrush, Brush);
UpdateSubscription(ref _subscribedToDashes, DashStyle);
}
void IWeakEventSubscriber<EventArgs>.OnEvent(object? sender, WeakEvent ev, EventArgs e)
{
if (ev == InvalidatedWeakEvent)
_invalidated?.Invoke(this, EventArgs.Empty);
}
}
}

17
src/Avalonia.Base/PropertyStore/BindingEntry.cs

@ -18,19 +18,19 @@ namespace Avalonia.PropertyStore
/// <typeparam name="T">The property type.</typeparam>
internal class BindingEntry<T> : IBindingEntry, IPriorityValueEntry<T>, IObserver<BindingValue<T>>
{
private readonly IAvaloniaObject _owner;
private IValueSink _sink;
private readonly AvaloniaObject _owner;
private ValueOwner<T> _sink;
private IDisposable? _subscription;
private bool _isSubscribed;
private bool _batchUpdate;
private Optional<T> _value;
public BindingEntry(
IAvaloniaObject owner,
AvaloniaObject owner,
StyledPropertyBase<T> property,
IObservable<BindingValue<T>> source,
BindingPriority priority,
IValueSink sink)
ValueOwner<T> sink)
{
_owner = owner;
Property = property;
@ -50,7 +50,7 @@ namespace Avalonia.PropertyStore
{
_batchUpdate = false;
if (_sink is ValueStore)
if (_sink.IsValueStore)
Start();
}
@ -113,16 +113,15 @@ namespace Avalonia.PropertyStore
}
}
public void Reparent(IValueSink sink) => _sink = sink;
public void Reparent(PriorityValue<T> parent) => _sink = new(parent);
public void RaiseValueChanged(
IValueSink sink,
IAvaloniaObject owner,
AvaloniaObject owner,
AvaloniaProperty property,
Optional<object?> oldValue,
Optional<object?> newValue)
{
sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner,
(AvaloniaProperty<T>)property,
oldValue.Cast<T>(),

13
src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs

@ -18,14 +18,14 @@ namespace Avalonia.PropertyStore
/// <typeparam name="T">The property type.</typeparam>
internal class ConstantValueEntry<T> : IPriorityValueEntry<T>, IConstantValueEntry
{
private IValueSink _sink;
private ValueOwner<T> _sink;
private Optional<T> _value;
public ConstantValueEntry(
StyledPropertyBase<T> property,
T value,
BindingPriority priority,
IValueSink sink)
ValueOwner<T> sink)
{
Property = property;
_value = value;
@ -37,7 +37,7 @@ namespace Avalonia.PropertyStore
StyledPropertyBase<T> property,
Optional<T> value,
BindingPriority priority,
IValueSink sink)
ValueOwner<T> sink)
{
Property = property;
_value = value;
@ -62,17 +62,16 @@ namespace Avalonia.PropertyStore
_sink.Completed(Property, this, oldValue);
}
public void Reparent(IValueSink sink) => _sink = sink;
public void Reparent(PriorityValue<T> sink) => _sink = new(sink);
public void Start() { }
public void RaiseValueChanged(
IValueSink sink,
IAvaloniaObject owner,
AvaloniaObject owner,
AvaloniaProperty property,
Optional<object?> oldValue,
Optional<object?> newValue)
{
sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner,
(AvaloniaProperty<T>)property,
oldValue.Cast<T>(),

2
src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs

@ -5,7 +5,6 @@
/// </summary>
internal interface IPriorityValueEntry : IValue
{
void Reparent(IValueSink sink);
}
/// <summary>
@ -14,5 +13,6 @@
/// <typeparam name="T">The property type.</typeparam>
internal interface IPriorityValueEntry<T> : IPriorityValueEntry, IValue<T>
{
void Reparent(PriorityValue<T> parent);
}
}

3
src/Avalonia.Base/PropertyStore/IValue.cs

@ -11,8 +11,7 @@ namespace Avalonia.PropertyStore
Optional<object?> GetValue();
void Start();
void RaiseValueChanged(
IValueSink sink,
IAvaloniaObject owner,
AvaloniaObject owner,
AvaloniaProperty property,
Optional<object?> oldValue,
Optional<object?> newValue);

17
src/Avalonia.Base/PropertyStore/IValueSink.cs

@ -1,17 +0,0 @@
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
/// <summary>
/// Represents an entity that can receive change notifications in a <see cref="ValueStore"/>.
/// </summary>
internal interface IValueSink
{
void ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change);
void Completed<T>(
StyledPropertyBase<T> property,
IPriorityValueEntry entry,
Optional<T> oldValue);
}
}

5
src/Avalonia.Base/PropertyStore/LocalValueEntry.cs

@ -25,13 +25,12 @@ namespace Avalonia.PropertyStore
public void Start() { }
public void RaiseValueChanged(
IValueSink sink,
IAvaloniaObject owner,
AvaloniaObject owner,
AvaloniaProperty property,
Optional<object?> oldValue,
Optional<object?> newValue)
{
sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner,
(AvaloniaProperty<T>)property,
oldValue.Cast<T>(),

67
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,10 +23,10 @@ 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>, IBatchUpdate
{
private readonly IAvaloniaObject _owner;
private readonly IValueSink _sink;
private readonly AvaloniaObject _owner;
private readonly ValueStore _store;
private readonly List<IPriorityValueEntry<T>> _entries = new List<IPriorityValueEntry<T>>();
private readonly Func<IAvaloniaObject, T, T>? _coerceValue;
private Optional<T> _localValue;
@ -28,13 +35,13 @@ namespace Avalonia.PropertyStore
private bool _batchUpdate;
public PriorityValue(
IAvaloniaObject owner,
AvaloniaObject owner,
StyledPropertyBase<T> property,
IValueSink sink)
ValueStore store)
{
_owner = owner;
Property = property;
_sink = sink;
_store = store;
if (property.HasCoercion)
{
@ -44,11 +51,11 @@ namespace Avalonia.PropertyStore
}
public PriorityValue(
IAvaloniaObject owner,
AvaloniaObject owner,
StyledPropertyBase<T> property,
IValueSink sink,
ValueStore store,
IPriorityValueEntry<T> existing)
: this(owner, property, sink)
: this(owner, property, store)
{
existing.Reparent(this);
_entries.Add(existing);
@ -75,9 +82,9 @@ namespace Avalonia.PropertyStore
}
public PriorityValue(
IAvaloniaObject owner,
AvaloniaObject owner,
StyledPropertyBase<T> property,
IValueSink sink,
ValueStore sink,
LocalValueEntry<T> existing)
: this(owner, property, sink)
{
@ -148,7 +155,7 @@ namespace Avalonia.PropertyStore
else
{
var insert = FindInsertPoint(priority);
var entry = new ConstantValueEntry<T>(Property, value, priority, this);
var entry = new ConstantValueEntry<T>(Property, value, priority, new ValueOwner<T>(this));
_entries.Insert(insert, entry);
result = entry;
}
@ -165,7 +172,7 @@ namespace Avalonia.PropertyStore
public BindingEntry<T> AddBinding(IObservable<BindingValue<T>> source, BindingPriority priority)
{
var binding = new BindingEntry<T>(_owner, Property, source, priority, this);
var binding = new BindingEntry<T>(_owner, Property, source, priority, new(this));
var insert = FindInsertPoint(binding.Priority);
_entries.Insert(insert, binding);
@ -186,13 +193,12 @@ namespace Avalonia.PropertyStore
public void Start() => UpdateEffectiveValue(null);
public void RaiseValueChanged(
IValueSink sink,
IAvaloniaObject owner,
AvaloniaObject owner,
AvaloniaProperty property,
Optional<object?> oldValue,
Optional<object?> newValue)
{
sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner,
(AvaloniaProperty<T>)property,
oldValue.Cast<T>(),
@ -200,7 +206,7 @@ namespace Avalonia.PropertyStore
Priority));
}
void IValueSink.ValueChanged<TValue>(AvaloniaPropertyChangedEventArgs<TValue> change)
public void ValueChanged<TValue>(AvaloniaPropertyChangedEventArgs<TValue> change)
{
if (change.Priority == BindingPriority.LocalValue)
{
@ -213,22 +219,15 @@ namespace Avalonia.PropertyStore
}
}
void IValueSink.Completed<TValue>(
StyledPropertyBase<TValue> property,
IPriorityValueEntry entry,
Optional<TValue> oldValue)
public void Completed(IPriorityValueEntry entry, Optional<T> oldValue)
{
_entries.Remove((IPriorityValueEntry<T>)entry);
if (oldValue is Optional<T> o)
{
UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>(
_owner,
Property,
o,
default,
entry.Priority));
}
UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>(
_owner,
Property,
oldValue,
default,
entry.Priority));
}
private int FindInsertPoint(BindingPriority priority)
@ -308,7 +307,7 @@ namespace Avalonia.PropertyStore
var old = _value;
_value = value;
_sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
_store.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
_owner,
Property,
old,
@ -319,7 +318,7 @@ namespace Avalonia.PropertyStore
{
change.MarkNonEffectiveValue();
change.SetOldValue(default);
_sink.ValueChanged(change);
_store.ValueChanged(change);
}
}
}

45
src/Avalonia.Base/PropertyStore/ValueOwner.cs

@ -0,0 +1,45 @@
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
/// <summary>
/// Represents a union type of <see cref="ValueStore"/> and <see cref="PriorityValue{T}"/>,
/// which are the valid owners of a value store <see cref="IValue"/>.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
internal readonly struct ValueOwner<T>
{
private readonly ValueStore? _store;
private readonly PriorityValue<T>? _priorityValue;
public ValueOwner(ValueStore o)
{
_store = o;
_priorityValue = null;
}
public ValueOwner(PriorityValue<T> v)
{
_store = null;
_priorityValue = v;
}
public bool IsValueStore => _store is not null;
public void Completed(StyledPropertyBase<T> property, IPriorityValueEntry entry, Optional<T> oldValue)
{
if (_store is not null)
_store?.Completed(property, entry, oldValue);
else
_priorityValue!.Completed(entry, oldValue);
}
public void ValueChanged(AvaloniaPropertyChangedEventArgs<T> e)
{
if (_store is not null)
_store?.ValueChanged(e);
else
_priorityValue!.ValueChanged(e);
}
}
}

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

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

7
src/Avalonia.Base/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)

44
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>
@ -177,19 +172,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 +192,7 @@ namespace Avalonia
/// <inheritdoc/>
internal override IDisposable? RouteSetValue(
IAvaloniaObject o,
AvaloniaObject o,
object? value,
BindingPriority priority)
{
@ -221,7 +216,7 @@ namespace Avalonia
/// <inheritdoc/>
internal override IDisposable RouteBind(
IAvaloniaObject o,
AvaloniaObject o,
IObservable<BindingValue<object?>> source,
BindingPriority priority)
{
@ -232,11 +227,36 @@ namespace Avalonia
/// <inheritdoc/>
internal override void RouteInheritanceParentChanged(
AvaloniaObject o,
IAvaloniaObject? oldParent)
AvaloniaObject? oldParent)
{
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);
}
}

29
src/Avalonia.Base/Utilities/IWeakEventSubscriber.cs

@ -9,4 +9,31 @@ namespace Avalonia.Utilities;
public interface IWeakEventSubscriber<in TEventArgs> where TEventArgs : EventArgs
{
void OnEvent(object? sender, WeakEvent ev, TEventArgs e);
}
}
public sealed class WeakEventSubscriber<TEventArgs> : IWeakEventSubscriber<TEventArgs> where TEventArgs : EventArgs
{
public event Action<object?, WeakEvent, TEventArgs>? Event;
void IWeakEventSubscriber<TEventArgs>.OnEvent(object? sender, WeakEvent ev, TEventArgs e)
{
Event?.Invoke(sender, ev, e);
}
}
public sealed class TargetWeakEventSubscriber<TTarget, TEventArgs> : IWeakEventSubscriber<TEventArgs> where TEventArgs : EventArgs
{
private readonly TTarget _target;
private readonly Action<TTarget, object?, WeakEvent, TEventArgs> _dispatchFunc;
public TargetWeakEventSubscriber(TTarget target, Action<TTarget, object?, WeakEvent, TEventArgs> dispatchFunc)
{
_target = target;
_dispatchFunc = dispatchFunc;
}
void IWeakEventSubscriber<TEventArgs>.OnEvent(object? sender, WeakEvent ev, TEventArgs e)
{
_dispatchFunc(_target, sender, ev, e);
}
}

138
src/Avalonia.Base/Utilities/WeakEvent.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
@ -36,7 +37,7 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
{
if (!_subscriptions.TryGetValue(target, out var subscription))
_subscriptions.Add(target, subscription = new Subscription(this, target));
subscription.Add(new WeakReference<IWeakEventSubscriber<TEventArgs>>(subscriber));
subscription.Add(subscriber);
}
public void Unsubscribe(TSender target, IWeakEventSubscriber<TEventArgs> subscriber)
@ -51,11 +52,59 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
private readonly TSender _target;
private readonly Action _compact;
private WeakReference<IWeakEventSubscriber<TEventArgs>>?[] _data =
new WeakReference<IWeakEventSubscriber<TEventArgs>>[16];
private int _count;
struct Entry
{
WeakReference<IWeakEventSubscriber<TEventArgs>>? _reference;
int _hashCode;
public Entry(IWeakEventSubscriber<TEventArgs> r)
{
if (r == null)
{
_reference = null;
_hashCode = 0;
return;
}
_hashCode = r.GetHashCode();
_reference = new WeakReference<IWeakEventSubscriber<TEventArgs>>(r);
}
public bool IsEmpty
{
get
{
if (_reference == null)
return true;
if (_reference.TryGetTarget(out _))
return false;
_reference = null;
return true;
}
}
public bool TryGetTarget([MaybeNullWhen(false)]out IWeakEventSubscriber<TEventArgs> target)
{
if (_reference == null)
{
target = null!;
return false;
}
return _reference.TryGetTarget(out target);
}
public bool Equals(IWeakEventSubscriber<TEventArgs> r)
{
if (_reference == null || r.GetHashCode() != _hashCode)
return false;
return _reference.TryGetTarget(out var target) && target == r;
}
}
private readonly Action _unsubscribe;
private readonly WeakHashList<IWeakEventSubscriber<TEventArgs>> _list = new();
private bool _compactScheduled;
private bool _destroyed;
public Subscription(WeakEvent<TSender, TEventArgs> ev, TSender target)
{
@ -67,48 +116,27 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
void Destroy()
{
if(_destroyed)
return;
_destroyed = true;
_unsubscribe();
_ev._subscriptions.Remove(_target);
}
public void Add(WeakReference<IWeakEventSubscriber<TEventArgs>> s)
{
if (_count == _data.Length)
{
//Extend capacity
var extendedData = new WeakReference<IWeakEventSubscriber<TEventArgs>>?[_data.Length * 2];
Array.Copy(_data, extendedData, _data.Length);
_data = extendedData;
}
_data[_count] = s;
_count++;
}
public void Add(IWeakEventSubscriber<TEventArgs> s) => _list.Add(s);
public void Remove(IWeakEventSubscriber<TEventArgs> s)
{
var removed = false;
for (int c = 0; c < _count; ++c)
{
var reference = _data[c];
if (reference != null && reference.TryGetTarget(out var instance) && instance == s)
{
_data[c] = null;
removed = true;
}
}
if (removed)
{
_list.Remove(s);
if(_list.IsEmpty)
Destroy();
else if(_list.NeedCompact && _compactScheduled)
ScheduleCompact();
}
}
void ScheduleCompact()
{
if(_compactScheduled)
if(_compactScheduled || _destroyed)
return;
_compactScheduled = true;
Dispatcher.UIThread.Post(_compact, DispatcherPriority.Background);
@ -116,43 +144,27 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
void Compact()
{
if(!_compactScheduled)
return;
_compactScheduled = false;
int empty = -1;
for (var c = 0; c < _count; c++)
{
var r = _data[c];
//Mark current index as first empty
if (r == null && empty == -1)
empty = c;
//If current element isn't null and we have an empty one
if (r != null && empty != -1)
{
_data[c] = null;
_data[empty] = r;
empty++;
}
}
if (empty != -1)
_count = empty;
if (_count == 0)
_list.Compact();
if (_list.IsEmpty)
Destroy();
}
void OnEvent(object? sender, TEventArgs eventArgs)
{
var needCompact = false;
for (var c = 0; c < _count; c++)
var alive = _list.GetAlive();
if(alive == null)
Destroy();
else
{
var r = _data[c];
if (r?.TryGetTarget(out var sub) == true)
sub!.OnEvent(_target, _ev, eventArgs);
else
needCompact = true;
foreach(var item in alive.Span)
item.OnEvent(_target, _ev, eventArgs);
WeakHashList<IWeakEventSubscriber<TEventArgs>>.ReturnToSharedPool(alive);
if(_list.NeedCompact && !_compactScheduled)
ScheduleCompact();
}
if (needCompact)
ScheduleCompact();
}
}

238
src/Avalonia.Base/Utilities/WeakHashList.cs

@ -0,0 +1,238 @@
using System;
using System.Collections.Generic;
using Avalonia.Collections.Pooled;
namespace Avalonia.Utilities;
internal class WeakHashList<T> where T : class
{
private struct Key
{
public WeakReference<T>? Weak;
public T? Strong;
public int HashCode;
public static Key MakeStrong(T r) => new()
{
HashCode = r.GetHashCode(),
Strong = r
};
public static Key MakeWeak(T r) => new()
{
HashCode = r.GetHashCode(),
Weak = new WeakReference<T>(r)
};
public override int GetHashCode() => HashCode;
}
class KeyComparer : IEqualityComparer<Key>
{
public bool Equals(Key x, Key y)
{
if (x.HashCode != y.HashCode)
return false;
if (x.Strong != null)
{
if (y.Strong != null)
return x.Strong == y.Strong;
if (y.Weak == null)
return false;
return y.Weak.TryGetTarget(out var weakTarget) && weakTarget == x.Strong;
}
else if (y.Strong != null)
{
if (x.Weak == null)
return false;
return x.Weak.TryGetTarget(out var weakTarget) && weakTarget == y.Strong;
}
else
{
if (x.Weak == null || x.Weak.TryGetTarget(out var xTarget) == false)
return y.Weak?.TryGetTarget(out _) != true;
return y.Weak?.TryGetTarget(out var yTarget) == true && xTarget == yTarget;
}
}
public int GetHashCode(Key obj) => obj.HashCode;
public static KeyComparer Instance = new();
}
Dictionary<Key, int>? _dic;
WeakReference<T>?[]? _arr;
int _arrCount;
public bool IsEmpty => _dic == null || _dic.Count == 0;
public bool NeedCompact { get; private set; }
public void Add(T item)
{
if (_dic != null)
{
var strongKey = Key.MakeStrong(item);
if (_dic.TryGetValue(strongKey, out var cnt))
_dic[strongKey] = cnt + 1;
else
_dic[Key.MakeWeak(item)] = 1;
return;
}
if (_arr == null)
_arr = new WeakReference<T>[8];
if (_arrCount < _arr.Length)
{
_arr[_arrCount] = new WeakReference<T>(item);
_arrCount++;
return;
}
// Check if something is dead
for (var c = 0; c < _arrCount; c++)
{
if (_arr[c]!.TryGetTarget(out _) == false)
{
_arr[c] = new WeakReference<T>(item);
return;
}
}
_dic = new Dictionary<Key, int>(KeyComparer.Instance);
foreach (var existing in _arr)
{
if (existing!.TryGetTarget(out var target))
Add(target);
}
Add(item);
_arr = null;
}
public void Remove(T item)
{
if (_arr != null)
{
for (var c = 0; c < _arr.Length; c++)
{
if (_arr[c]?.TryGetTarget(out var target) == true && target == item)
{
_arr[c] = null;
Compact();
return;
}
}
}
else if (_dic != null)
{
var strongKey = Key.MakeStrong(item);
if (_dic.TryGetValue(strongKey, out var cnt))
{
if (cnt > 1)
{
_dic[strongKey] = cnt - 1;
return;
}
}
_dic.Remove(strongKey);
}
}
private void ArrCompact()
{
if (_arr != null)
{
int empty = -1;
for (var c = 0; c < _arrCount; c++)
{
var r = _arr[c];
//Mark current index as first empty
if (r == null && empty == -1)
empty = c;
//If current element isn't null and we have an empty one
if (r != null && empty != -1)
{
_arr[c] = null;
_arr[empty] = r;
empty++;
}
}
if (empty != -1)
_arrCount = empty;
}
}
public void Compact()
{
if (_dic != null)
{
PooledList<Key>? toRemove = null;
foreach (var kvp in _dic)
{
if (kvp.Key.Weak?.TryGetTarget(out _) != true)
(toRemove ??= new PooledList<Key>()).Add(kvp.Key);
}
if (toRemove != null)
{
foreach (var k in toRemove)
_dic.Remove(k);
toRemove.Dispose();
}
}
}
private static readonly Stack<PooledList<T>> s_listPool = new();
public static void ReturnToSharedPool(PooledList<T> list)
{
list.Clear();
s_listPool.Push(list);
}
public PooledList<T>? GetAlive(Func<PooledList<T>>? factory = null)
{
PooledList<T>? pooled = null;
if (_arr != null)
{
bool needCompact = false;
for (var c = 0; c < _arrCount; c++)
{
if (_arr[c]?.TryGetTarget(out var target) == true)
(pooled ??= factory?.Invoke()
?? (s_listPool.Count > 0
? s_listPool.Pop()
: new PooledList<T>())).Add(target!);
else
{
_arr[c] = null;
needCompact = true;
}
}
if(needCompact)
ArrCompact();
return pooled;
}
if (_dic != null)
{
foreach (var kvp in _dic)
{
if (kvp.Key.Weak?.TryGetTarget(out var target) == true)
(pooled ??= factory?.Invoke()
?? (s_listPool.Count > 0
? s_listPool.Pop()
: new PooledList<T>()))
.Add(target!);
else
NeedCompact = true;
}
}
return pooled;
}
}

27
src/Avalonia.Base/ValueStore.cs

@ -21,16 +21,15 @@ namespace Avalonia
/// - For a single binding it will be an instance of <see cref="BindingEntry{T}"/>
/// - For all other cases it will be an instance of <see cref="PriorityValue{T}"/>
/// </remarks>
internal class ValueStore : IValueSink
internal class ValueStore
{
private readonly AvaloniaObject _owner;
private readonly IValueSink _sink;
private readonly AvaloniaPropertyValueStore<IValue> _values;
private BatchUpdate? _batchUpdate;
public ValueStore(AvaloniaObject owner)
{
_sink = _owner = owner;
_owner = owner;
_values = new AvaloniaPropertyValueStore<IValue>();
}
@ -122,7 +121,7 @@ namespace Avalonia
}
else
{
var entry = new ConstantValueEntry<T>(property, value, priority, this);
var entry = new ConstantValueEntry<T>(property, value, priority, new(this));
AddValue(property, entry);
NotifyValueChanged<T>(property, default, value, priority);
result = entry;
@ -151,7 +150,7 @@ namespace Avalonia
}
else
{
var entry = new BindingEntry<T>(_owner, property, source, priority, this);
var entry = new BindingEntry<T>(_owner, property, source, priority, new(this));
AddValue(property, entry);
return entry;
}
@ -187,7 +186,7 @@ namespace Avalonia
// so there's no way to mark them for removal at the end of a batch update. Instead convert
// them to a constant value entry with Unset priority in the event of a local value being
// cleared during a batch update.
var sentinel = new ConstantValueEntry<T>(property, Optional<T>.Empty, BindingPriority.Unset, _sink);
var sentinel = new ConstantValueEntry<T>(property, Optional<T>.Empty, BindingPriority.Unset, new(this));
_values.SetValue(property, sentinel);
}
@ -196,11 +195,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();
}
@ -222,7 +221,7 @@ namespace Avalonia
return null;
}
void IValueSink.ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
public void ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
if (_batchUpdate is object)
{
@ -233,11 +232,11 @@ namespace Avalonia
}
else
{
_sink.ValueChanged(change);
_owner.ValueChanged(change);
}
}
void IValueSink.Completed<T>(
public void Completed<T>(
StyledPropertyBase<T> property,
IPriorityValueEntry entry,
Optional<T> oldValue)
@ -248,7 +247,7 @@ namespace Avalonia
if (_batchUpdate is null)
{
_values.Remove(property);
_sink.Completed(property, entry, oldValue);
_owner.Completed(property, entry, oldValue);
}
else
{
@ -352,7 +351,7 @@ namespace Avalonia
{
if (_batchUpdate is null)
{
_sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
_owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
_owner,
property,
oldValue,
@ -451,7 +450,7 @@ namespace Avalonia
};
// Call _sink.ValueChanged with an appropriately typed AvaloniaPropertyChangedEventArgs<T>.
slot.RaiseValueChanged(_owner._sink, _owner._owner, entry.property, oldValue, newValue);
slot.RaiseValueChanged(_owner._owner, entry.property, oldValue, newValue);
// During batch update values can't be removed immediately because they're needed to raise
// the _sink.ValueChanged notification. They instead mark themselves for removal by setting

21
src/Avalonia.Base/Visual.cs

@ -97,12 +97,18 @@ namespace Avalonia
/// </summary>
public static readonly StyledProperty<int> ZIndexProperty =
AvaloniaProperty.Register<Visual, int>(nameof(ZIndex));
private static readonly WeakEvent<IAffectsRender, EventArgs> InvalidatedWeakEvent =
WeakEvent.Register<IAffectsRender>(
(s, h) => s.Invalidated += h,
(s, h) => s.Invalidated -= h);
private Rect _bounds;
private TransformedBounds? _transformedBounds;
private IRenderRoot? _visualRoot;
private IVisual? _visualParent;
private bool _hasMirrorTransform;
private TargetWeakEventSubscriber<Visual, EventArgs>? _affectsRenderWeakSubscriber;
/// <summary>
/// Initializes static members of the <see cref="Visual"/> class.
@ -369,12 +375,21 @@ namespace Avalonia
{
if (e.OldValue is IAffectsRender oldValue)
{
WeakEventHandlerManager.Unsubscribe<EventArgs, T>(oldValue, nameof(oldValue.Invalidated), sender.AffectsRenderInvalidated);
if (sender._affectsRenderWeakSubscriber != null)
InvalidatedWeakEvent.Unsubscribe(oldValue, sender._affectsRenderWeakSubscriber);
}
if (e.NewValue is IAffectsRender newValue)
{
WeakEventHandlerManager.Subscribe<IAffectsRender, EventArgs, T>(newValue, nameof(newValue.Invalidated), sender.AffectsRenderInvalidated);
if (sender._affectsRenderWeakSubscriber == null)
{
sender._affectsRenderWeakSubscriber = new TargetWeakEventSubscriber<Visual, EventArgs>(
sender, static (target, _, _, _) =>
{
target.InvalidateVisual();
});
}
InvalidatedWeakEvent.Subscribe(newValue, sender._affectsRenderWeakSubscriber);
}
sender.InvalidateVisual();
@ -625,8 +640,6 @@ namespace Avalonia
OnVisualParentChanged(old, value);
}
private void AffectsRenderInvalidated(object? sender, EventArgs e) => InvalidateVisual();
/// <summary>
/// Called when the <see cref="VisualChildren"/> collection changes.
/// </summary>

5
src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt

@ -1 +1,4 @@
Total Issues: 0
Compat issues with assembly Avalonia.Controls.DataGrid:
MembersMustExist : Member 'protected void Avalonia.Controls.DataGridCheckBoxColumn.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.DataGridTextColumn.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
Total Issues: 2

2
src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs

@ -46,7 +46,7 @@ namespace Avalonia.Controls
set => SetValue(IsThreeStateProperty, value);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

4
src/Avalonia.Controls.DataGrid/DataGridColumn.cs

@ -192,14 +192,14 @@ namespace Avalonia.Controls
set => SetValue(IsVisibleProperty, value);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == IsVisibleProperty)
{
OwningGrid?.OnColumnVisibleStateChanging(this);
var isVisible = change.NewValue.GetValueOrDefault<bool>();
var isVisible = change.GetNewValue<bool>();
if (_headerCell != null)
{

2
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@ -1062,7 +1062,7 @@ namespace Avalonia.Controls
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (change.Property == DataContextProperty)
{

2
src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs

@ -119,7 +119,7 @@ namespace Avalonia.Controls
set => SetValue(ForegroundProperty, value);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

17
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -1,11 +1,17 @@
Compat issues with assembly Avalonia.Controls:
MembersMustExist : Member 'protected void Avalonia.Controls.Button.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.ButtonSpinner.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.CalendarDatePicker.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.ContextMenu.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Controls.DropDown' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Controls.DropDownItem' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.Expander.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Controls.IMenuItem.StaysOpenOnClick' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Controls.IMenuItem.StaysOpenOnClick.get()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.IMenuItem.StaysOpenOnClick.set(System.Boolean)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.ItemsRepeater.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.DirectProperty<Avalonia.Controls.NumericUpDown, System.Double> Avalonia.DirectProperty<Avalonia.Controls.NumericUpDown, System.Double> Avalonia.Controls.NumericUpDown.ValueProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Double> Avalonia.StyledProperty<System.Double> Avalonia.Controls.NumericUpDown.IncrementProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Double> Avalonia.StyledProperty<System.Double> Avalonia.Controls.NumericUpDown.MaximumProperty' does not exist in the implementation but it does exist in the contract.
@ -30,7 +36,9 @@ MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDown.Value.set
MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDownValueChangedEventArgs..ctor(Avalonia.Interactivity.RoutedEvent, System.Double, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.NewValue.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.ProgressBar.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Boolean> Avalonia.StyledProperty<System.Boolean> Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.Slider.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AttachedProperty<Avalonia.Media.FontFamily> Avalonia.AttachedProperty<Avalonia.Media.FontFamily> Avalonia.Controls.TextBlock.FontFamilyProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AttachedProperty<Avalonia.Media.FontStyle> Avalonia.AttachedProperty<Avalonia.Media.FontStyle> Avalonia.Controls.TextBlock.FontStyleProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AttachedProperty<Avalonia.Media.FontWeight> Avalonia.AttachedProperty<Avalonia.Media.FontWeight> Avalonia.Controls.TextBlock.FontWeightProperty' does not exist in the implementation but it does exist in the contract.
@ -51,10 +59,12 @@ MembersMustExist : Member 'public void Avalonia.Controls.TextBlock.SetFontSize(A
MembersMustExist : Member 'public void Avalonia.Controls.TextBlock.SetFontStyle(Avalonia.Controls.Control, Avalonia.Media.FontStyle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.TextBlock.SetFontWeight(Avalonia.Controls.Control, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.TextBlock.SetForeground(Avalonia.Controls.Control, Avalonia.Media.IBrush)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.TextBox.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.TopLevel' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract.
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Viewbox' does not inherit from base type 'Avalonia.Controls.Decorator' in the implementation but it does in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract.
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Window' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.Window.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.WindowBase' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler<Avalonia.Controls.ApplicationLifetimes.ShutdownRequestedEventArgs> Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler<Avalonia.Controls.ApplicationLifetimes.ShutdownRequestedEventArgs>)' is present in the implementation but not in the contract.
@ -65,12 +75,17 @@ MembersMustExist : Member 'public System.Action<Avalonia.Size> Avalonia.Controls
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.set(System.Action<Avalonia.Size>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.Notifications.WindowNotificationManager.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.Platform.ITopLevelNativeMenuExporter.SetNativeMenu(Avalonia.Controls.NativeMenu)' is present in the contract but not in the implementation.
MembersMustExist : Member 'protected void Avalonia.Controls.Presenters.ScrollContentPresenter.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected Avalonia.Media.FormattedText Avalonia.Controls.Presenters.TextPresenter.CreateFormattedText()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.FormattedText Avalonia.Controls.Presenters.TextPresenter.FormattedText.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Int32 Avalonia.Controls.Presenters.TextPresenter.GetCaretIndex(Avalonia.Point)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.Presenters.TextPresenter.InvalidateFormattedText()' does not exist in the implementation but it does exist in the contract.
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Primitives.PopupRoot' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.Primitives.ScrollBar.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.Primitives.SelectingItemsControl.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.Primitives.Track.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Platform.ExportWindowingSubsystemAttribute' does not exist in the implementation but it does exist in the contract.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.Screen Avalonia.Platform.IScreenImpl.ScreenFromPoint(Avalonia.PixelPoint)' is present in the implementation but not in the contract.
@ -94,4 +109,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract.
Total Issues: 95
Total Issues: 110

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

23
src/Avalonia.Controls/Button.cs

@ -413,7 +413,7 @@ namespace Avalonia.Controls
}
/// <inheritdoc/>
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@ -421,12 +421,13 @@ namespace Avalonia.Controls
{
if (((ILogical)this).IsAttachedToLogicalTree)
{
if (change.OldValue.GetValueOrDefault() is ICommand oldCommand)
var (oldValue, newValue) = change.GetOldAndNewValue<ICommand?>();
if (oldValue is ICommand oldCommand)
{
oldCommand.CanExecuteChanged -= CanExecuteChanged;
}
if (change.NewValue.GetValueOrDefault() is ICommand newCommand)
if (newValue is ICommand newCommand)
{
newCommand.CanExecuteChanged += CanExecuteChanged;
}
@ -440,7 +441,7 @@ namespace Avalonia.Controls
}
else if (change.Property == IsCancelProperty)
{
var isCancel = change.NewValue.GetValueOrDefault<bool>();
var isCancel = change.GetNewValue<bool>();
if (VisualRoot is IInputElement inputRoot)
{
@ -456,7 +457,7 @@ namespace Avalonia.Controls
}
else if (change.Property == IsDefaultProperty)
{
var isDefault = change.NewValue.GetValueOrDefault<bool>();
var isDefault = change.GetNewValue<bool>();
if (VisualRoot is IInputElement inputRoot)
{
@ -476,8 +477,7 @@ namespace Avalonia.Controls
}
else if (change.Property == FlyoutProperty)
{
var oldFlyout = change.OldValue.GetValueOrDefault() as FlyoutBase;
var newFlyout = change.NewValue.GetValueOrDefault() as FlyoutBase;
var (oldFlyout, newFlyout) = change.GetOldAndNewValue<FlyoutBase?>();
// If flyout is changed while one is already open, make sure we
// close the old one first
@ -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/ButtonSpinner.cs

@ -210,13 +210,13 @@ namespace Avalonia.Controls
}
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ButtonSpinnerLocationProperty)
{
UpdatePseudoClasses(change.NewValue.GetValueOrDefault<Location>());
UpdatePseudoClasses(change.GetNewValue<Location>());
}
}

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

4
src/Avalonia.Controls/ContextMenu.cs

@ -241,13 +241,13 @@ namespace Avalonia.Controls
}
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == WindowManagerAddShadowHintProperty && _popup != null)
{
_popup.WindowManagerAddShadowHint = change.NewValue.GetValueOrDefault<bool>();
_popup.WindowManagerAddShadowHint = change.GetNewValue<bool>();
}
}

2
src/Avalonia.Controls/Control.cs

@ -348,7 +348,7 @@ namespace Avalonia.Controls
}
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

2
src/Avalonia.Controls/Documents/Inline.cs

@ -55,7 +55,7 @@ namespace Avalonia.Controls.Documents
TextDecorations, Foreground, Background, BaselineAlignment);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

2
src/Avalonia.Controls/Documents/Run.cs

@ -71,7 +71,7 @@ namespace Avalonia.Controls.Documents
return text.Length;
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

2
src/Avalonia.Controls/Documents/TextElement.cs

@ -256,7 +256,7 @@ namespace Avalonia.Controls.Documents
/// </summary>
public event EventHandler? Invalidated;
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

4
src/Avalonia.Controls/Expander.cs

@ -106,13 +106,13 @@ namespace Avalonia.Controls
}
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ExpandDirectionProperty)
{
UpdatePseudoClasses(change.NewValue.GetValueOrDefault<ExpandDirection>());
UpdatePseudoClasses(change.GetNewValue<ExpandDirection>());
}
}

4
src/Avalonia.Controls/ItemsControl.cs

@ -341,13 +341,13 @@ namespace Avalonia.Controls
return new ItemsControlAutomationPeer(this);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ItemCountProperty)
{
UpdatePseudoClasses(change.NewValue.GetValueOrDefault<int>());
UpdatePseudoClasses(change.GetNewValue<int>());
}
}

2
src/Avalonia.Controls/MaskedTextBox.cs

@ -280,7 +280,7 @@ namespace Avalonia.Controls
base.OnLostFocus(e);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
void UpdateMaskProvider()
{

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;

4
src/Avalonia.Controls/Notifications/WindowNotificationManager.cs

@ -139,13 +139,13 @@ namespace Avalonia.Controls.Notifications
notificationControl.Close();
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == PositionProperty)
{
UpdatePseudoClasses(change.NewValue.GetValueOrDefault<NotificationPosition>());
UpdatePseudoClasses(change.GetNewValue<NotificationPosition>());
}
}

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

2
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -403,7 +403,7 @@ namespace Avalonia.Controls.Presenters
}
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
switch (change.Property.Name)

2
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -469,7 +469,7 @@ namespace Avalonia.Controls.Presenters
}
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (change.Property == OffsetProperty && !_arranging)
{

2
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -776,7 +776,7 @@ namespace Avalonia.Controls.Presenters
_caretTimer.Tick -= CaretTimerTick;
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

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

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

@ -194,13 +194,13 @@ namespace Avalonia.Controls.Primitives
}
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == OrientationProperty)
{
UpdatePseudoClasses(change.NewValue.GetValueOrDefault<Orientation>());
UpdatePseudoClasses(change.GetNewValue<Orientation>());
}
else if (change.Property == AllowAutoHideProperty)
{

20
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);
}
}
@ -533,9 +537,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)
{
@ -585,7 +589,7 @@ namespace Avalonia.Controls.Primitives
}
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@ -595,7 +599,7 @@ namespace Avalonia.Controls.Primitives
}
if (change.Property == ItemsProperty && _updateState is null && _selection is object)
{
var newValue = change.NewValue.GetValueOrDefault<IEnumerable>();
var newValue = change.GetNewValue<IEnumerable>();
_selection.Source = newValue;
if (newValue is null)
@ -605,7 +609,7 @@ namespace Avalonia.Controls.Primitives
}
else if (change.Property == SelectionModeProperty && _selection is object)
{
var newValue = change.NewValue.GetValueOrDefault<SelectionMode>();
var newValue = change.GetNewValue<SelectionMode>();
_selection.SingleSelect = !newValue.HasAllFlags(SelectionMode.Multiple);
}
else if (change.Property == WrapSelectionProperty)

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

@ -291,13 +291,13 @@ namespace Avalonia.Controls.Primitives
return arrangeSize;
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == OrientationProperty)
{
UpdatePseudoClasses(change.NewValue.GetValueOrDefault<Orientation>());
UpdatePseudoClasses(change.GetNewValue<Orientation>());
}
}

6
src/Avalonia.Controls/ProgressBar.cs

@ -178,17 +178,17 @@ namespace Avalonia.Controls
return base.ArrangeOverride(finalSize);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == IsIndeterminateProperty)
{
UpdatePseudoClasses(change.NewValue.GetValueOrDefault<bool>(), null);
UpdatePseudoClasses(change.GetNewValue<bool>(), null);
}
else if (change.Property == OrientationProperty)
{
UpdatePseudoClasses(null, change.NewValue.GetValueOrDefault<Orientation>());
UpdatePseudoClasses(null, change.GetNewValue<Orientation>());
}
}

4
src/Avalonia.Controls/RepeatButton.cs

@ -70,11 +70,11 @@ namespace Avalonia.Controls
_repeatTimer?.Stop();
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == IsPressedProperty && change.NewValue.GetValueOrDefault<bool>() == false)
if (change.Property == IsPressedProperty && change.GetNewValue<bool>() == false)
{
StopTimer();
}

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

@ -20,7 +20,7 @@ namespace Avalonia.Controls
/// Represents a data-driven collection control that incorporates a flexible layout system,
/// custom views, and virtualization.
/// </summary>
public class ItemsRepeater : Panel, IChildIndexProvider, IWeakEventSubscriber<EventArgs>
public class ItemsRepeater : Panel, IChildIndexProvider
{
/// <summary>
/// Defines the <see cref="HorizontalCacheLength"/> property.
@ -60,6 +60,7 @@ namespace Avalonia.Controls
private readonly ViewManager _viewManager;
private readonly ViewportManager _viewportManager;
private readonly TargetWeakEventSubscriber<ItemsRepeater, EventArgs> _layoutWeakSubscriber;
private IEnumerable? _items;
private VirtualizingLayoutContext? _layoutContext;
private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged;
@ -74,6 +75,15 @@ namespace Avalonia.Controls
/// </summary>
public ItemsRepeater()
{
_layoutWeakSubscriber = new TargetWeakEventSubscriber<ItemsRepeater, EventArgs>(
this, static (target, _, ev, _) =>
{
if (ev == AttachedLayout.ArrangeInvalidatedWeakEvent)
target.InvalidateArrange();
else if (ev == AttachedLayout.MeasureInvalidatedWeakEvent)
target.InvalidateMeasure();
});
_viewManager = new ViewManager(this);
_viewportManager = new ViewportManager(this);
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Once);
@ -257,10 +267,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 +286,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()
@ -391,11 +405,7 @@ namespace Avalonia.Controls
var newBounds = element.Bounds;
virtInfo.ArrangeBounds = newBounds;
if (!virtInfo.IsRegisteredAsAnchorCandidate)
{
_viewportManager.RegisterScrollAnchorCandidate(element);
virtInfo.IsRegisteredAsAnchorCandidate = true;
}
_viewportManager.RegisterScrollAnchorCandidate(element, virtInfo);
}
}
@ -420,12 +430,11 @@ namespace Avalonia.Controls
_viewportManager.ResetScrollers();
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (change.Property == ItemsProperty)
{
var oldEnumerable = change.OldValue.GetValueOrDefault<IEnumerable>();
var newEnumerable = change.NewValue.GetValueOrDefault<IEnumerable>();
var (oldEnumerable, newEnumerable) = change.GetOldAndNewValue<IEnumerable?>();
if (oldEnumerable != newEnumerable)
{
@ -440,23 +449,21 @@ namespace Avalonia.Controls
}
else if (change.Property == ItemTemplateProperty)
{
OnItemTemplateChanged(
change.OldValue.GetValueOrDefault<IDataTemplate>(),
change.NewValue.GetValueOrDefault<IDataTemplate>());
var (oldvalue, newValue) = change.GetOldAndNewValue<IDataTemplate?>();
OnItemTemplateChanged(oldvalue, newValue);
}
else if (change.Property == LayoutProperty)
{
OnLayoutChanged(
change.OldValue.GetValueOrDefault<AttachedLayout>(),
change.NewValue.GetValueOrDefault<AttachedLayout>());
var (oldvalue, newValue) = change.GetOldAndNewValue<AttachedLayout>();
OnLayoutChanged(oldvalue, newValue);
}
else if (change.Property == HorizontalCacheLengthProperty)
{
_viewportManager.HorizontalCacheLength = change.NewValue.GetValueOrDefault<double>();
_viewportManager.HorizontalCacheLength = change.GetNewValue<double>();
}
else if (change.Property == VerticalCacheLengthProperty)
{
_viewportManager.VerticalCacheLength = change.NewValue.GetValueOrDefault<double>();
_viewportManager.VerticalCacheLength = change.GetNewValue<double>();
}
base.OnPropertyChanged(change);
@ -480,7 +487,7 @@ namespace Avalonia.Controls
_processingItemsSourceChange.Action == NotifyCollectionChangedAction.Reset);
_viewManager.ClearElement(element, isClearedDueToCollectionChange);
_viewportManager.OnElementCleared(element);
_viewportManager.OnElementCleared(element, GetVirtualizationInfo(element));
}
private int GetElementIndexImpl(IControl element)
@ -491,7 +498,7 @@ namespace Avalonia.Controls
if (parent == this)
{
var virtInfo = TryGetVirtualizationInfo(element);
return _viewManager.GetElementIndex(virtInfo);
return _viewManager.GetElementIndex(virtInfo!);
}
return -1;
@ -728,8 +735,8 @@ namespace Avalonia.Controls
{
oldValue.UninitializeForContext(LayoutContext);
AttachedLayout.MeasureInvalidatedWeakEvent.Unsubscribe(oldValue, this);
AttachedLayout.ArrangeInvalidatedWeakEvent.Unsubscribe(oldValue, this);
AttachedLayout.MeasureInvalidatedWeakEvent.Unsubscribe(oldValue, _layoutWeakSubscriber);
AttachedLayout.ArrangeInvalidatedWeakEvent.Unsubscribe(oldValue, _layoutWeakSubscriber);
// Walk through all the elements and make sure they are cleared
foreach (var element in Children)
@ -747,8 +754,8 @@ namespace Avalonia.Controls
{
newValue.InitializeForContext(LayoutContext);
AttachedLayout.MeasureInvalidatedWeakEvent.Subscribe(newValue, this);
AttachedLayout.ArrangeInvalidatedWeakEvent.Subscribe(newValue, this);
AttachedLayout.MeasureInvalidatedWeakEvent.Subscribe(newValue, _layoutWeakSubscriber);
AttachedLayout.ArrangeInvalidatedWeakEvent.Subscribe(newValue, _layoutWeakSubscriber);
}
bool isVirtualizingLayout = newValue != null && newValue is VirtualizingLayout;
@ -798,15 +805,7 @@ namespace Avalonia.Controls
{
_viewportManager.OnBringIntoViewRequested(e);
}
void IWeakEventSubscriber<EventArgs>.OnEvent(object? sender, WeakEvent ev, EventArgs e)
{
if(ev == AttachedLayout.ArrangeInvalidatedWeakEvent)
InvalidateArrange();
else if (ev == AttachedLayout.MeasureInvalidatedWeakEvent)
InvalidateMeasure();
}
private VirtualizingLayoutContext GetLayoutContext()
{
if (_layoutContext == null)

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

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

29
src/Avalonia.Controls/Repeater/ViewportManager.cs

@ -249,9 +249,10 @@ namespace Avalonia.Controls
virtInfo.IsRegisteredAsAnchorCandidate = false;
}
public void OnElementCleared(IControl element)
public void OnElementCleared(IControl element, VirtualizationInfo virtInfo)
{
_scroller?.UnregisterAnchorCandidate(element);
virtInfo.IsRegisteredAsAnchorCandidate = false;
}
public void OnOwnerMeasuring()
@ -358,9 +359,12 @@ namespace Avalonia.Controls
{
foreach (var child in _owner.Children)
{
if (child != targetChild)
var info = ItemsRepeater.GetVirtualizationInfo(child);
if (child != targetChild && info.IsRegisteredAsAnchorCandidate)
{
_scroller.UnregisterAnchorCandidate(child);
info.IsRegisteredAsAnchorCandidate = false;
}
}
}
@ -377,9 +381,13 @@ namespace Avalonia.Controls
}
}
public void RegisterScrollAnchorCandidate(IControl element)
public void RegisterScrollAnchorCandidate(IControl element, VirtualizationInfo virtInfo)
{
_scroller?.RegisterAnchorCandidate(element);
if (!virtInfo.IsRegisteredAsAnchorCandidate)
{
_scroller?.RegisterAnchorCandidate(element);
virtInfo.IsRegisteredAsAnchorCandidate = true;
}
}
private IControl? GetImmediateChildOfRepeater(IControl descendant)
@ -405,15 +413,18 @@ namespace Avalonia.Controls
_isBringIntoViewInProgress = false;
_makeAnchorElement = null;
// Undo the anchor deregistrations done by OnBringIntoViewRequested.
if (_scroller is object)
{
foreach (var child in _owner.Children)
{
var info = ItemsRepeater.GetVirtualizationInfo(child);
if (info.IsRealized && info.IsHeldByLayout)
// The item brought into view is still registered - don't register it more than once.
if (info.IsRealized && info.IsHeldByLayout && !info.IsRegisteredAsAnchorCandidate)
{
_scroller.RegisterAnchorCandidate(child);
info.IsRegisteredAsAnchorCandidate = true;
}
}
}
@ -430,7 +441,13 @@ namespace Avalonia.Controls
{
foreach (var child in _owner.Children)
{
_scroller.UnregisterAnchorCandidate(child);
var info = ItemsRepeater.GetVirtualizationInfo(child);
if (info.IsRegisteredAsAnchorCandidate)
{
_scroller.UnregisterAnchorCandidate(child);
info.IsRegisteredAsAnchorCandidate = false;
}
}
_scroller = null;

11
src/Avalonia.Controls/Slider.cs

@ -361,21 +361,24 @@ 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);
}
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == OrientationProperty)
{
UpdatePseudoClasses(change.NewValue.GetValueOrDefault<Orientation>());
UpdatePseudoClasses(change.GetNewValue<Orientation>());
}
}

11
src/Avalonia.Controls/SplitButton/SplitButton.cs

@ -276,19 +276,21 @@ namespace Avalonia.Controls
}
/// <inheritdoc/>
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> e)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == CommandProperty)
{
if (_isAttachedToLogicalTree)
{
// Must unregister events here while a reference to the old command still exists
if (e.OldValue.GetValueOrDefault() is ICommand oldCommand)
var (oldValue, newValue) = e.GetOldAndNewValue<ICommand?>();
if (oldValue is ICommand oldCommand)
{
oldCommand.CanExecuteChanged -= CanExecuteChanged;
}
if (e.NewValue.GetValueOrDefault() is ICommand newCommand)
if (newValue is ICommand newCommand)
{
newCommand.CanExecuteChanged += CanExecuteChanged;
}
@ -302,8 +304,7 @@ namespace Avalonia.Controls
}
else if (e.Property == FlyoutProperty)
{
var oldFlyout = e.OldValue.GetValueOrDefault() as FlyoutBase;
var newFlyout = e.NewValue.GetValueOrDefault() as FlyoutBase;
var (oldFlyout, newFlyout) = e.GetOldAndNewValue<FlyoutBase?>();
// If flyout is changed while one is already open, make sure we
// close the old one first

2
src/Avalonia.Controls/SplitButton/ToggleSplitButton.cs

@ -90,7 +90,7 @@ namespace Avalonia.Controls
////////////////////////////////////////////////////////////////////////
/// <inheritdoc/>
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> e)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == IsCheckedProperty)
{

2
src/Avalonia.Controls/TextBlock.cs

@ -639,7 +639,7 @@ namespace Avalonia.Controls
private static bool IsValidLineHeight(double lineHeight) => double.IsNaN(lineHeight) || lineHeight > 0;
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

11
src/Avalonia.Controls/TextBox.cs

@ -585,7 +585,7 @@ namespace Avalonia.Controls
_imClient.SetPresenter(null, null);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@ -594,7 +594,7 @@ namespace Avalonia.Controls
UpdatePseudoclasses();
UpdateCommandStates();
}
else if (change.Property == IsUndoEnabledProperty && change.NewValue.GetValueOrDefault<bool>() == false)
else if (change.Property == IsUndoEnabledProperty && change.GetNewValue<bool>() == false)
{
// from docs at
// https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled:
@ -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);
}
}

17
src/Avalonia.Controls/TopLevel.cs

@ -34,8 +34,7 @@ namespace Avalonia.Controls
ICloseable,
IStyleHost,
ILogicalRoot,
ITextInputMethodRoot,
IWeakEventSubscriber<ResourcesChangedEventArgs>
ITextInputMethodRoot
{
/// <summary>
/// Defines the <see cref="ClientSize"/> property.
@ -93,6 +92,7 @@ namespace Avalonia.Controls
private WindowTransparencyLevel _actualTransparencyLevel;
private ILayoutManager? _layoutManager;
private Border? _transparencyFallbackBorder;
private TargetWeakEventSubscriber<TopLevel, ResourcesChangedEventArgs>? _resourcesChangesSubscriber;
/// <summary>
/// Initializes static members of the <see cref="TopLevel"/> class.
@ -192,7 +192,13 @@ namespace Avalonia.Controls
if (((IStyleHost)this).StylingParent is IResourceHost applicationResources)
{
ResourcesChangedWeakEvent.Subscribe(applicationResources, this);
_resourcesChangesSubscriber = new TargetWeakEventSubscriber<TopLevel, ResourcesChangedEventArgs>(
this, static (target, _, _, e) =>
{
((ILogical)target).NotifyResourcesChanged(e);
});
ResourcesChangedWeakEvent.Subscribe(applicationResources, _resourcesChangesSubscriber);
}
impl.LostFocus += PlatformImpl_LostFocus;
@ -297,11 +303,6 @@ namespace Avalonia.Controls
/// <inheritdoc/>
IMouseDevice? IInputRoot.MouseDevice => PlatformImpl?.MouseDevice;
void IWeakEventSubscriber<ResourcesChangedEventArgs>.OnEvent(object? sender, WeakEvent ev, ResourcesChangedEventArgs e)
{
((ILogical)this).NotifyResourcesChanged(e);
}
/// <summary>
/// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary>

2
src/Avalonia.Controls/TransitioningContentControl.cs

@ -61,7 +61,7 @@ public class TransitioningContentControl : ContentControl
_lastTransitionCts?.Cancel();
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

8
src/Avalonia.Controls/TrayIcon.cs

@ -206,7 +206,7 @@ namespace Avalonia.Controls
}
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@ -216,15 +216,15 @@ namespace Avalonia.Controls
}
else if (change.Property == IsVisibleProperty)
{
_impl?.SetIsVisible(change.NewValue.GetValueOrDefault<bool>());
_impl?.SetIsVisible(change.GetNewValue<bool>());
}
else if (change.Property == ToolTipTextProperty)
{
_impl?.SetToolTipText(change.NewValue.GetValueOrDefault<string?>());
_impl?.SetToolTipText(change.GetNewValue<string?>());
}
else if (change.Property == MenuProperty)
{
_impl?.MenuExporter?.SetNativeMenu(change.NewValue.GetValueOrDefault<NativeMenu>());
_impl?.MenuExporter?.SetNativeMenu(change.GetNewValue<NativeMenu?>());
}
}

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

4
src/Avalonia.Controls/Viewbox.cs

@ -82,13 +82,13 @@ namespace Avalonia.Controls
set => _containerVisual.RenderTransform = value;
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ChildProperty)
{
_containerVisual.Child = change.NewValue.GetValueOrDefault<IControl>();
_containerVisual.Child = change.GetNewValue<IControl>();
InvalidateMeasure();
}
}

6
src/Avalonia.Controls/Window.cs

@ -1019,16 +1019,16 @@ namespace Avalonia.Controls
/// </remarks>
protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e);
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == SystemDecorationsProperty)
{
var typedNewValue = change.NewValue.GetValueOrDefault<SystemDecorations>();
var (typedOldValue, typedNewValue) = change.GetOldAndNewValue<SystemDecorations>();
PlatformImpl?.SetSystemDecorations(typedNewValue);
var o = change.OldValue.GetValueOrDefault<SystemDecorations>() == SystemDecorations.Full;
var o = typedOldValue == SystemDecorations.Full;
var n = typedNewValue == SystemDecorations.Full;
if (o != n)

4
src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs

@ -92,7 +92,7 @@ namespace Avalonia.Diagnostics.Controls
set => SetValue(HighlightProperty, value);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@ -102,7 +102,7 @@ namespace Avalonia.Diagnostics.Controls
{
_isUpdatingThickness = true;
var value = change.NewValue.GetValueOrDefault<Thickness>();
var value = change.GetNewValue<Thickness>();
Left = value.Left;
Top = value.Top;

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

2
src/Avalonia.Themes.Default/SimpleTheme.cs

@ -116,7 +116,7 @@ namespace Avalonia.Themes.Default
return false;
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ModeProperty)

2
src/Avalonia.Themes.Fluent/FluentTheme.cs

@ -78,7 +78,7 @@ namespace Avalonia.Themes.Fluent
set => SetValue(DensityStyleProperty, value);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ModeProperty)

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

@ -138,7 +138,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");

2
src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs

@ -23,7 +23,7 @@ namespace Avalonia.Win32.WinRT
[DllImport("api-ms-win-core-winrt-string-l1-1-0.dll",
CallingConvention = CallingConvention.StdCall, PreserveSig = false)]
internal static extern unsafe IntPtr WindowsDeleteString(IntPtr hString);
internal static extern unsafe void WindowsDeleteString(IntPtr hString);
[DllImport("Windows.UI.Composition", EntryPoint = "DllGetActivationFactory",
CallingConvention = CallingConvention.StdCall, PreserveSig = false)]

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

15
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs

@ -109,25 +109,26 @@ namespace Avalonia.Base.UnitTests
public List<AvaloniaPropertyChangedEventArgs> Changes { get; }
public List<AvaloniaPropertyChangedEventArgs> CoreChanges { get; }
protected override void OnPropertyChangedCore<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change)
{
CoreChanges.Add(Clone(change));
base.OnPropertyChangedCore(change);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
Changes.Add(Clone(change));
base.OnPropertyChanged(change);
}
private static AvaloniaPropertyChangedEventArgs<T> Clone<T>(AvaloniaPropertyChangedEventArgs<T> change)
private static AvaloniaPropertyChangedEventArgs Clone(AvaloniaPropertyChangedEventArgs change)
{
var result = new AvaloniaPropertyChangedEventArgs<T>(
var e = (AvaloniaPropertyChangedEventArgs<string>)change;
var result = new AvaloniaPropertyChangedEventArgs<string>(
change.Sender,
change.Property,
change.OldValue,
change.NewValue,
e.Property,
e.OldValue,
e.NewValue,
change.Priority);
if (!change.IsEffectiveValueChange)

23
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,46 +147,46 @@ 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(
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)
{
throw new NotImplementedException();
}
internal override ISetterInstance CreateSetterInstance(IStyleable target, object value)
{
throw new NotImplementedException();
}
}
private class Class1 : AvaloniaObject

49
tests/Avalonia.Base.UnitTests/PriorityValueTests.cs

@ -10,8 +10,8 @@ namespace Avalonia.Base.UnitTests
{
public class PriorityValueTests
{
private static readonly IValueSink NullSink = new MockSink();
private static readonly IAvaloniaObject Owner = Mock.Of<IAvaloniaObject>();
private static readonly AvaloniaObject Owner = new AvaloniaObject();
private static readonly ValueStore ValueStore = new ValueStore(Owner);
private static readonly StyledProperty<string> TestProperty = new StyledProperty<string>(
"Test",
typeof(PriorityValueTests),
@ -23,12 +23,12 @@ namespace Avalonia.Base.UnitTests
var target = new PriorityValue<string>(
Owner,
TestProperty,
NullSink,
ValueStore,
new ConstantValueEntry<string>(
TestProperty,
"1",
BindingPriority.StyleTrigger,
NullSink));
new(ValueStore)));
Assert.Equal("1", target.GetValue().Value);
Assert.Equal(BindingPriority.StyleTrigger, target.Priority);
@ -40,7 +40,7 @@ namespace Avalonia.Base.UnitTests
var target = new PriorityValue<string>(
Owner,
TestProperty,
NullSink);
ValueStore);
target.SetValue("animation", BindingPriority.Animation);
target.SetValue("local", BindingPriority.LocalValue);
@ -60,7 +60,7 @@ namespace Avalonia.Base.UnitTests
var target = new PriorityValue<string>(
Owner,
TestProperty,
NullSink);
ValueStore);
target.SetValue("1", BindingPriority.LocalValue);
target.SetValue("2", BindingPriority.LocalValue);
@ -74,7 +74,7 @@ namespace Avalonia.Base.UnitTests
var target = new PriorityValue<string>(
Owner,
TestProperty,
NullSink);
ValueStore);
target.SetValue("1", BindingPriority.Style);
target.SetValue("2", BindingPriority.Animation);
@ -93,7 +93,7 @@ namespace Avalonia.Base.UnitTests
var target = new PriorityValue<string>(
Owner,
TestProperty,
NullSink);
ValueStore);
Assert.Equal(BindingPriority.Unset, target.Priority);
target.SetValue("style", BindingPriority.Style);
@ -109,7 +109,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void Binding_With_Same_Priority_Should_Be_Appended()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
var source2 = new Source("2");
@ -129,7 +129,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void Binding_With_Higher_Priority_Should_Be_Appended()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
var source2 = new Source("2");
@ -149,7 +149,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void Binding_With_Lower_Priority_Should_Be_Prepended()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
var source2 = new Source("2");
@ -169,7 +169,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void Second_Binding_With_Lower_Priority_Should_Be_Inserted_In_Middle()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
var source2 = new Source("2");
var source3 = new Source("3");
@ -191,7 +191,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void Competed_Binding_Should_Be_Removed()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
var source2 = new Source("2");
var source3 = new Source("3");
@ -214,7 +214,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void Value_Should_Come_From_Last_Entry()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
var source2 = new Source("2");
var source3 = new Source("3");
@ -229,7 +229,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void LocalValue_Should_Override_LocalValue_Binding()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
target.AddBinding(source1, BindingPriority.LocalValue).Start();
@ -241,7 +241,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void LocalValue_Should_Override_Style_Binding()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
target.AddBinding(source1, BindingPriority.Style).Start();
@ -253,7 +253,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void LocalValue_Should_Not_Override_Animation_Binding()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
target.AddBinding(source1, BindingPriority.Animation).Start();
@ -265,7 +265,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void NonAnimated_Value_Should_Be_Correct_1()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
var source2 = new Source("2");
var source3 = new Source("3");
@ -281,7 +281,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void NonAnimated_Value_Should_Be_Correct_2()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
var source2 = new Source("2");
var source3 = new Source("3");
@ -310,16 +310,5 @@ namespace Avalonia.Base.UnitTests
public void OnCompleted() => _observer.OnCompleted();
}
private class MockSink : IValueSink
{
public void Completed<T>(StyledPropertyBase<T> property, IPriorityValueEntry entry, Optional<T> oldValue)
{
}
public void ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
}
}
}
}

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

@ -5,6 +5,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Diagnostics;
using Avalonia.Styling;
using Moq;
using Xunit;
@ -105,51 +106,51 @@ namespace Avalonia.Base.UnitTests.Styling
[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)

45
tests/Avalonia.Benchmarks/Visuals/VisualAffectsRenderBenchmarks.cs

@ -0,0 +1,45 @@
using Avalonia.Controls;
using Avalonia.Media;
using BenchmarkDotNet.Attributes;
namespace Avalonia.Benchmarks.Visuals;
[MemoryDiagnoser]
public class VisualAffectsRenderBenchmarks
{
private readonly TestVisual _target;
private readonly IPen _pen;
public VisualAffectsRenderBenchmarks()
{
_target = new TestVisual();
_pen = new Pen(Brushes.Black);
}
[Benchmark]
public void SetPropertyThatAffectsRender()
{
_target.Pen = _pen;
_target.Pen = null;
}
private class TestVisual : Visual
{
/// <summary>
/// Defines the <see cref="Pen"/> property.
/// </summary>
public static readonly StyledProperty<IPen> PenProperty =
AvaloniaProperty.Register<Border, IPen>(nameof(Pen));
public IPen Pen
{
get => GetValue(PenProperty);
set => SetValue(PenProperty, value);
}
static TestVisual()
{
AffectsRender<TestVisual>(PenProperty);
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save