Skip to content

Commit 3b64ef4

Browse files
MrJuljmacato
andcommitted
Avoid multiple resource change notifications (#18488)
Co-authored-by: Jumar Macato <[email protected]>
1 parent e011861 commit 3b64ef4

16 files changed

+184
-87
lines changed

src/Avalonia.Base/Controls/IResourceHost.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,12 @@ public interface IResourceHost : IResourceNode
2929
/// </remarks>
3030
void NotifyHostedResourcesChanged(ResourcesChangedEventArgs e);
3131
}
32+
33+
// TODO12: merge with IResourceHost
34+
internal interface IResourceHost2 : IResourceHost
35+
{
36+
event EventHandler<ResourcesChangedToken> ResourcesChanged2;
37+
38+
void NotifyHostedResourcesChanged(ResourcesChangedToken token);
39+
}
3240
}

src/Avalonia.Base/Controls/ResourceDictionary.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public object? this[object key]
4040
set
4141
{
4242
Inner[key] = value;
43-
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
43+
RaiseResourcesChanged();
4444
}
4545
}
4646

@@ -138,7 +138,7 @@ public sealed override bool HasResources
138138
public void Add(object key, object? value)
139139
{
140140
Inner.Add(key, value);
141-
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
141+
RaiseResourcesChanged();
142142
}
143143

144144
public void AddDeferred(object key, Func<IServiceProvider?, object?> factory)
@@ -155,7 +155,7 @@ public void Clear()
155155
if (_inner?.Count > 0)
156156
{
157157
_inner.Clear();
158-
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
158+
RaiseResourcesChanged();
159159
}
160160
}
161161

@@ -165,7 +165,7 @@ public bool Remove(object key)
165165
{
166166
if (_inner?.Remove(key) == true)
167167
{
168-
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
168+
RaiseResourcesChanged();
169169
return true;
170170
}
171171

@@ -303,7 +303,7 @@ public void EnsureCapacity(int capacity)
303303
{
304304
if ((_inner as ICollection<KeyValuePair<object, object?>>)?.Remove(item) == true)
305305
{
306-
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
306+
RaiseResourcesChanged();
307307
return true;
308308
}
309309

@@ -345,7 +345,7 @@ protected sealed override void OnAddOwner(IResourceHost owner)
345345

346346
if (hasResources)
347347
{
348-
owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
348+
owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create());
349349
}
350350
}
351351

@@ -372,7 +372,7 @@ protected sealed override void OnRemoveOwner(IResourceHost owner)
372372

373373
if (hasResources)
374374
{
375-
owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
375+
owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create());
376376
}
377377
}
378378

src/Avalonia.Base/Controls/ResourceNodeExtensions.cs

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ public ResourceObservable(
152152

153153
protected override void Initialize()
154154
{
155-
_target.ResourcesChanged += ResourcesChanged;
155+
_target.SubscribeToResourcesChanged(ResourcesChanged, ResourcesChanged2);
156+
156157
if (_target is IThemeVariantHost themeVariantHost)
157158
{
158159
themeVariantHost.ActualThemeVariantChanged += ActualThemeVariantChanged;
@@ -161,7 +162,8 @@ protected override void Initialize()
161162

162163
protected override void Deinitialize()
163164
{
164-
_target.ResourcesChanged -= ResourcesChanged;
165+
_target.UnsubscribeFromResourcesChanged(ResourcesChanged, ResourcesChanged2);
166+
165167
if (_target is IThemeVariantHost themeVariantHost)
166168
{
167169
themeVariantHost.ActualThemeVariantChanged -= ActualThemeVariantChanged;
@@ -178,6 +180,11 @@ private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e)
178180
PublishNext(GetValue());
179181
}
180182

183+
private void ResourcesChanged2(object? sender, ResourcesChangedToken token)
184+
{
185+
PublishNext(GetValue());
186+
}
187+
181188
private void ActualThemeVariantChanged(object? sender, EventArgs e)
182189
{
183190
PublishNext(GetValue());
@@ -217,10 +224,8 @@ protected override void Initialize()
217224
_target.OwnerChanged += OwnerChanged;
218225
_owner = _target.Owner;
219226

220-
if (_owner is not null)
221-
{
222-
_owner.ResourcesChanged += ResourcesChanged;
223-
}
227+
_owner?.SubscribeToResourcesChanged(ResourcesChanged, ResourcesChanged2);
228+
224229
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost)
225230
{
226231
themeVariantHost.ActualThemeVariantChanged += ActualThemeVariantChanged;
@@ -231,10 +236,8 @@ protected override void Deinitialize()
231236
{
232237
_target.OwnerChanged -= OwnerChanged;
233238

234-
if (_owner is not null)
235-
{
236-
_owner.ResourcesChanged -= ResourcesChanged;
237-
}
239+
_owner?.UnsubscribeFromResourcesChanged(ResourcesChanged, ResourcesChanged2);
240+
238241
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost)
239242
{
240243
themeVariantHost.ActualThemeVariantChanged -= ActualThemeVariantChanged;
@@ -261,21 +264,17 @@ private void PublishNext()
261264

262265
private void OwnerChanged(object? sender, EventArgs e)
263266
{
264-
if (_owner is not null)
265-
{
266-
_owner.ResourcesChanged -= ResourcesChanged;
267-
}
267+
_owner?.UnsubscribeFromResourcesChanged(ResourcesChanged, ResourcesChanged2);
268+
268269
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost)
269270
{
270271
themeVariantHost.ActualThemeVariantChanged -= ActualThemeVariantChanged;
271272
}
272273

273274
_owner = _target.Owner;
274275

275-
if (_owner is not null)
276-
{
277-
_owner.ResourcesChanged += ResourcesChanged;
278-
}
276+
_owner?.SubscribeToResourcesChanged(ResourcesChanged, ResourcesChanged2);
277+
279278
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost2)
280279
{
281280
themeVariantHost2.ActualThemeVariantChanged += ActualThemeVariantChanged;
@@ -294,6 +293,11 @@ private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e)
294293
PublishNext();
295294
}
296295

296+
private void ResourcesChanged2(object? sender, ResourcesChangedToken token)
297+
{
298+
PublishNext();
299+
}
300+
297301
private object? GetValue()
298302
{
299303
var theme = _overrideThemeVariant ?? (_target.Owner as IThemeVariantHost)?.ActualThemeVariant;

src/Avalonia.Base/Controls/ResourceProvider.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ private set
4545

4646
protected void RaiseResourcesChanged()
4747
{
48-
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
48+
Owner?.NotifyHostedResourcesChanged(ResourcesChangedToken.Create());
4949
}
5050

5151
/// <summary>
@@ -57,7 +57,7 @@ protected virtual void OnAddOwner(IResourceHost owner)
5757
{
5858
if (HasResources)
5959
{
60-
owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
60+
owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create());
6161
}
6262
}
6363

@@ -70,7 +70,7 @@ protected virtual void OnRemoveOwner(IResourceHost owner)
7070
{
7171
if (HasResources)
7272
{
73-
owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
73+
owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create());
7474
}
7575
}
7676

src/Avalonia.Base/Controls/ResourcesChangedEventArgs.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Avalonia.Controls
44
{
5+
// TODO12: change this to be a struct, remove ResourcesChangedToken
56
public class ResourcesChangedEventArgs : EventArgs
67
{
78
public static new readonly ResourcesChangedEventArgs Empty = new ResourcesChangedEventArgs();
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System;
2+
using Avalonia.LogicalTree;
3+
4+
namespace Avalonia.Controls;
5+
6+
internal static class ResourcesChangedHelper
7+
{
8+
internal static void NotifyHostedResourcesChanged(this IResourceHost host, ResourcesChangedToken token)
9+
{
10+
if (host is IResourceHost2 host2)
11+
host2.NotifyHostedResourcesChanged(token);
12+
else
13+
host.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
14+
}
15+
16+
internal static void NotifyResourcesChanged(this ILogical logical, ResourcesChangedToken token)
17+
{
18+
if (logical is StyledElement styledElement)
19+
styledElement.NotifyResourcesChanged(token);
20+
else
21+
logical.NotifyResourcesChanged(ResourcesChangedEventArgs.Empty);
22+
}
23+
24+
internal static void SubscribeToResourcesChanged(
25+
this IResourceHost host,
26+
EventHandler<ResourcesChangedEventArgs> handler,
27+
EventHandler<ResourcesChangedToken> handler2)
28+
{
29+
if (host is IResourceHost2 host2)
30+
host2.ResourcesChanged2 += handler2;
31+
else
32+
host.ResourcesChanged += handler;
33+
}
34+
35+
internal static void UnsubscribeFromResourcesChanged(
36+
this IResourceHost host,
37+
EventHandler<ResourcesChangedEventArgs> handler,
38+
EventHandler<ResourcesChangedToken> handler2)
39+
{
40+
if (host is IResourceHost2 host2)
41+
host2.ResourcesChanged2 -= handler2;
42+
else
43+
host.ResourcesChanged -= handler;
44+
}
45+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Threading;
2+
3+
namespace Avalonia.Controls;
4+
5+
internal record struct ResourcesChangedToken(int SequenceNumber)
6+
{
7+
private static int s_lastSequenceNumber;
8+
9+
public static ResourcesChangedToken Create()
10+
=> new(Interlocked.Increment(ref s_lastSequenceNumber));
11+
}

src/Avalonia.Base/StyledElement.cs

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class StyledElement : Animatable,
2929
IDataContextProvider,
3030
ILogical,
3131
IThemeVariantHost,
32+
IResourceHost2,
3233
IStyleHost,
3334
ISetLogicalParent,
3435
ISetInheritanceParent,
@@ -93,6 +94,8 @@ public class StyledElement : Animatable,
9394
private AvaloniaObject? _templatedParent;
9495
private bool _dataContextUpdating;
9596
private ControlTheme? _implicitTheme;
97+
private EventHandler<ResourcesChangedToken>? _resourcesChanged2;
98+
private ResourcesChangedToken _lastResourcesChangedToken;
9699

97100
/// <summary>
98101
/// Initializes static members of the <see cref="StyledElement"/> class.
@@ -147,6 +150,12 @@ public StyledElement()
147150
/// </summary>
148151
public event EventHandler<ResourcesChangedEventArgs>? ResourcesChanged;
149152

153+
event EventHandler<ResourcesChangedToken>? IResourceHost2.ResourcesChanged2
154+
{
155+
add => _resourcesChanged2 += value;
156+
remove => _resourcesChanged2 -= value;
157+
}
158+
150159
/// <inheritdoc />
151160
public event EventHandler? ActualThemeVariantChanged;
152161

@@ -427,10 +436,15 @@ void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
427436
}
428437

429438
/// <inheritdoc/>
430-
void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e) => NotifyResourcesChanged(e);
439+
void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e)
440+
=> NotifyResourcesChanged(ResourcesChangedToken.Create());
431441

432442
/// <inheritdoc/>
433-
void IResourceHost.NotifyHostedResourcesChanged(ResourcesChangedEventArgs e) => NotifyResourcesChanged(e);
443+
void IResourceHost.NotifyHostedResourcesChanged(ResourcesChangedEventArgs e)
444+
=> NotifyResourcesChanged(ResourcesChangedToken.Create());
445+
446+
void IResourceHost2.NotifyHostedResourcesChanged(ResourcesChangedToken token)
447+
=> NotifyResourcesChanged(token);
434448

435449
/// <inheritdoc/>
436450
public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
@@ -485,7 +499,7 @@ void ISetLogicalParent.SetParent(ILogical? parent)
485499
// non-rooted control beacuse it's unlikely that dynamic resources need to be
486500
// correct until the control is added to the tree, and it causes a *lot* of
487501
// notifications.
488-
NotifyResourcesChanged();
502+
NotifyResourcesChanged(ResourcesChangedToken.Create());
489503
}
490504

491505
RaisePropertyChanged(ParentProperty, old, Parent);
@@ -538,20 +552,18 @@ protected virtual void LogicalChildrenCollectionChanged(object? sender, NotifyCo
538552
/// <summary>
539553
/// Notifies child controls that a change has been made to resources that apply to them.
540554
/// </summary>
541-
/// <param name="e">The event args.</param>
542-
internal virtual void NotifyChildResourcesChanged(ResourcesChangedEventArgs e)
555+
/// <param name="token">The change token.</param>
556+
internal virtual void NotifyChildResourcesChanged(ResourcesChangedToken token)
543557
{
544558
if (_logicalChildren is object)
545559
{
546560
var count = _logicalChildren.Count;
547561

548562
if (count > 0)
549563
{
550-
e ??= ResourcesChangedEventArgs.Empty;
551-
552564
for (var i = 0; i < count; ++i)
553565
{
554-
_logicalChildren[i].NotifyResourcesChanged(e);
566+
_logicalChildren[i].NotifyResourcesChanged(token);
555567
}
556568
}
557569
}
@@ -880,7 +892,7 @@ private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
880892

881893
ReevaluateImplicitTheme();
882894
ApplyStyling();
883-
NotifyResourcesChanged(propagate: false);
895+
NotifyResourcesChanged(ResourcesChangedToken.Create(), propagate: false);
884896

885897
OnAttachedToLogicalTree(e);
886898
AttachedToLogicalTree?.Invoke(this, e);
@@ -983,20 +995,24 @@ private void DetachStyles(IReadOnlyList<Style> styles)
983995
}
984996
}
985997

986-
private void NotifyResourcesChanged(
987-
ResourcesChangedEventArgs? e = null,
998+
internal void NotifyResourcesChanged(
999+
ResourcesChangedToken token,
9881000
bool propagate = true)
9891001
{
990-
if (ResourcesChanged is object)
1002+
// We already got a notification for this element, ignore.
1003+
if (token.Equals(_lastResourcesChangedToken))
9911004
{
992-
e ??= ResourcesChangedEventArgs.Empty;
993-
ResourcesChanged(this, e);
1005+
return;
9941006
}
9951007

1008+
_lastResourcesChangedToken = token;
1009+
1010+
_resourcesChanged2?.Invoke(this, token);
1011+
ResourcesChanged?.Invoke(this, ResourcesChangedEventArgs.Empty);
1012+
9961013
if (propagate)
9971014
{
998-
e ??= ResourcesChangedEventArgs.Empty;
999-
NotifyChildResourcesChanged(e);
1015+
NotifyChildResourcesChanged(token);
10001016
}
10011017
}
10021018

src/Avalonia.Base/Styling/StyleBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public IResourceDictionary Resources
5454

5555
if (hadResources || _resources.HasResources)
5656
{
57-
Owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
57+
Owner.NotifyHostedResourcesChanged(ResourcesChangedToken.Create());
5858
}
5959
}
6060
}

0 commit comments

Comments
 (0)