diff --git a/src/Avalonia.Base/Media/Pen.cs b/src/Avalonia.Base/Media/Pen.cs
index 65ba851100..bda0e5cb99 100644
--- a/src/Avalonia.Base/Media/Pen.cs
+++ b/src/Avalonia.Base/Media/Pen.cs
@@ -7,7 +7,7 @@ namespace Avalonia.Media
///
/// Describes how a stroke is drawn.
///
- public sealed class Pen : AvaloniaObject, IPen, IWeakEventSubscriber
+ public sealed class Pen : AvaloniaObject, IPen
{
///
/// Defines the property.
@@ -48,7 +48,8 @@ namespace Avalonia.Media
private EventHandler? _invalidated;
private IAffectsRender? _subscribedToBrush;
private IAffectsRender? _subscribedToDashes;
-
+ private TargetWeakEventSubscriber? _weakSubscriber;
+
///
/// Initializes a new instance of the class.
///
@@ -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(
+ 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.OnEvent(object? sender, WeakEvent ev, EventArgs e)
- {
- if (ev == InvalidatedWeakEvent)
- _invalidated?.Invoke(this, EventArgs.Empty);
- }
}
}
diff --git a/src/Avalonia.Base/Utilities/IWeakEventSubscriber.cs b/src/Avalonia.Base/Utilities/IWeakEventSubscriber.cs
index e48c0cb111..6cf8c605a7 100644
--- a/src/Avalonia.Base/Utilities/IWeakEventSubscriber.cs
+++ b/src/Avalonia.Base/Utilities/IWeakEventSubscriber.cs
@@ -9,4 +9,31 @@ namespace Avalonia.Utilities;
public interface IWeakEventSubscriber where TEventArgs : EventArgs
{
void OnEvent(object? sender, WeakEvent ev, TEventArgs e);
-}
\ No newline at end of file
+}
+
+public sealed class WeakEventSubscriber : IWeakEventSubscriber where TEventArgs : EventArgs
+{
+ public event Action
public static readonly StyledProperty ZIndexProperty =
AvaloniaProperty.Register(nameof(ZIndex));
+
+ private static readonly WeakEvent InvalidatedWeakEvent =
+ WeakEvent.Register(
+ (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? _affectsRenderWeakSubscriber;
///
/// Initializes static members of the class.
@@ -369,12 +375,21 @@ namespace Avalonia
{
if (e.OldValue is IAffectsRender oldValue)
{
- WeakEventHandlerManager.Unsubscribe(oldValue, nameof(oldValue.Invalidated), sender.AffectsRenderInvalidated);
+ if (sender._affectsRenderWeakSubscriber != null)
+ InvalidatedWeakEvent.Unsubscribe(oldValue, sender._affectsRenderWeakSubscriber);
}
if (e.NewValue is IAffectsRender newValue)
{
- WeakEventHandlerManager.Subscribe(newValue, nameof(newValue.Invalidated), sender.AffectsRenderInvalidated);
+ if (sender._affectsRenderWeakSubscriber == null)
+ {
+ sender._affectsRenderWeakSubscriber = new TargetWeakEventSubscriber(
+ 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();
-
///
/// Called when the collection changes.
///
diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
index 09c0e58332..c63adb0bf6 100644
--- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
+++ b/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.
///
- public class ItemsRepeater : Panel, IChildIndexProvider, IWeakEventSubscriber
+ public class ItemsRepeater : Panel, IChildIndexProvider
{
///
/// Defines the property.
@@ -60,6 +60,7 @@ namespace Avalonia.Controls
private readonly ViewManager _viewManager;
private readonly ViewportManager _viewportManager;
+ private readonly TargetWeakEventSubscriber _layoutWeakSubscriber;
private IEnumerable? _items;
private VirtualizingLayoutContext? _layoutContext;
private EventHandler? _childIndexChanged;
@@ -74,6 +75,15 @@ namespace Avalonia.Controls
///
public ItemsRepeater()
{
+ _layoutWeakSubscriber = new TargetWeakEventSubscriber(
+ 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);
@@ -728,8 +738,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 +757,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 +808,7 @@ namespace Avalonia.Controls
{
_viewportManager.OnBringIntoViewRequested(e);
}
-
- void IWeakEventSubscriber.OnEvent(object? sender, WeakEvent ev, EventArgs e)
- {
- if(ev == AttachedLayout.ArrangeInvalidatedWeakEvent)
- InvalidateArrange();
- else if (ev == AttachedLayout.MeasureInvalidatedWeakEvent)
- InvalidateMeasure();
- }
-
+
private VirtualizingLayoutContext GetLayoutContext()
{
if (_layoutContext == null)
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 75a34659a2..57fb82485c 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -34,8 +34,7 @@ namespace Avalonia.Controls
ICloseable,
IStyleHost,
ILogicalRoot,
- ITextInputMethodRoot,
- IWeakEventSubscriber
+ ITextInputMethodRoot
{
///
/// Defines the property.
@@ -93,6 +92,7 @@ namespace Avalonia.Controls
private WindowTransparencyLevel _actualTransparencyLevel;
private ILayoutManager? _layoutManager;
private Border? _transparencyFallbackBorder;
+ private TargetWeakEventSubscriber? _resourcesChangesSubscriber;
///
/// Initializes static members of the class.
@@ -192,7 +192,13 @@ namespace Avalonia.Controls
if (((IStyleHost)this).StylingParent is IResourceHost applicationResources)
{
- ResourcesChangedWeakEvent.Subscribe(applicationResources, this);
+ _resourcesChangesSubscriber = new TargetWeakEventSubscriber(
+ this, static (target, _, _, e) =>
+ {
+ ((ILogical)target).NotifyResourcesChanged(e);
+ });
+
+ ResourcesChangedWeakEvent.Subscribe(applicationResources, _resourcesChangesSubscriber);
}
impl.LostFocus += PlatformImpl_LostFocus;
@@ -297,11 +303,6 @@ namespace Avalonia.Controls
///
IMouseDevice? IInputRoot.MouseDevice => PlatformImpl?.MouseDevice;
- void IWeakEventSubscriber.OnEvent(object? sender, WeakEvent ev, ResourcesChangedEventArgs e)
- {
- ((ILogical)this).NotifyResourcesChanged(e);
- }
-
///
/// Gets or sets a value indicating whether access keys are shown in the window.
///
diff --git a/tests/Avalonia.Benchmarks/Visuals/VisualAffectsRenderBenchmarks.cs b/tests/Avalonia.Benchmarks/Visuals/VisualAffectsRenderBenchmarks.cs
new file mode 100644
index 0000000000..5b85a068d6
--- /dev/null
+++ b/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
+ {
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty PenProperty =
+ AvaloniaProperty.Register(nameof(Pen));
+
+ public IPen Pen
+ {
+ get => GetValue(PenProperty);
+ set => SetValue(PenProperty, value);
+ }
+
+ static TestVisual()
+ {
+ AffectsRender(PenProperty);
+ }
+ }
+}