From 7bacbc4d9beeb8c1d294164c1bdc49c714dee344 Mon Sep 17 00:00:00 2001 From: "Claude (Initial Force WPF Bot)" Date: Sun, 17 May 2026 18:50:31 +0200 Subject: [PATCH] fix(AdornerLayer): remove dead _layoutDirty infrastructure (CS0414) Follow-up to f703b12ae. The previous PR removed the unsafe _layoutDirty gate from OnLayoutUpdated but kept the field and its surrounding plumbing (Subscribe/Unsubscribe/OnAdornedElementLayoutUpdated + _subscribedElements HashSet) to keep that diff minimal and reversible. CI builds with TreatWarningsAsErrors=true, so the now-unused field trips CS0414 ("The field 'AdornerLayer._layoutDirty' is assigned but its value is never used") and blocks the if.81 publish. This is the planned cleanup. Removes: - _layoutDirty field (read-eliminated by the prior commit) - _subscribedElements HashSet - OnAdornedElementLayoutUpdated handler - SubscribeToElementLayout / UnsubscribeFromElementLayout helpers - All call sites that armed _layoutDirty (Add/Remove/Update/SetAdornerZOrder) - Per-element LayoutUpdated subscription from Add/Remove Net behavior unchanged from f703b12ae - the gate was already removed, so these write-only assignments and unread subscriptions had no observable effect. Co-Authored-By: Claude Opus 4.7 --- .../System/Windows/Documents/AdornerLayer.cs | 73 +------------------ 1 file changed, 1 insertion(+), 72 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/AdornerLayer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/AdornerLayer.cs index abe76c029d0..ffe5a0e9d31 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/AdornerLayer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/AdornerLayer.cs @@ -232,14 +232,6 @@ public void Remove(Adorner adorner) RemoveAdornerInfo(_zOrderMap, adorner, adornerInfo.ZOrder); _children.Remove(adorner); RemoveLogicalChild(adorner); - - // If no more adorners remain for this element, unsubscribe from its LayoutUpdated - // to break the AdornerLayer/UIElement retention cycle. - if (ElementMap[adorner.AdornedElement] == null) - { - UnsubscribeFromElementLayout(adorner.AdornedElement); - } - _layoutDirty = true; } /// @@ -261,7 +253,6 @@ public void Update() } } - _layoutDirty = true; UpdateAdorner(null); } @@ -285,7 +276,6 @@ public void Update(UIElement element) InvalidateAdorner((AdornerInfo)adornerInfos[i++]); } - _layoutDirty = true; UpdateAdorner(element); } @@ -612,15 +602,10 @@ internal void Add(Adorner adorner, int zOrder) AddAdornerInfo(ElementMap, adornerInfo, adorner.AdornedElement); - // Subscribe to the adorned element's LayoutUpdated so we can arm _layoutDirty - // only when something actually changes, rather than on every layer-level fire. - SubscribeToElementLayout(adorner.AdornedElement); - AddAdornerToVisualTree(adornerInfo, zOrder); AddLogicalChild(adorner); - _layoutDirty = true; UpdateAdorner(adorner.AdornedElement); } @@ -639,18 +624,6 @@ internal static void InvalidateAdorner(AdornerInfo adornerInfo) adornerInfo.SimpleTransform = default; } - // TODO: regression tests for OnLayoutUpdated fast path (no DRT harness available in fork): - // 1. EmptyAdornerLayer_OnLayoutUpdated_DoesNotCallUpdateAdorner - // Create an AdornerLayer, call OnLayoutUpdated — verify UpdateAdorner was NOT called - // (mock or subclass override) and _layoutDirty ends up false. - // 2. AdornerLayer_AddAdornerAfterIdle_TriggersUpdateAdorner - // Create empty layer, fire OnLayoutUpdated (empty fast-path, _layoutDirty→false), - // Add() an adorner, fire OnLayoutUpdated again — verify UpdateAdorner IS called. - // 3. AdornerLayer_AddRemoveDuringLayoutUpdated_NoStaleDirtyFlag - // Simulate Add() inside a LayoutUpdated handler that fires concurrently with the - // layer's own handler; confirm that by the time the next pass fires, the adorner - // is walked (ElementMap.Count > 0 path) and _layoutDirty is not stranded false. - /// /// OnLayoutUpdated event handler /// @@ -670,7 +643,7 @@ internal void OnLayoutUpdated(object sender, EventArgs args) return; } - // Non-empty layer: always run UpdateAdorner. A cached _layoutDirty flag + // Non-empty layer: always run UpdateAdorner. A cached dirty flag // cannot comprehensively observe every coordinate-space change that // requires re-walking adorners (ancestor RenderTransform changes, // ContentPresenter re-templating that swaps the subtree between an @@ -682,40 +655,6 @@ internal void OnLayoutUpdated(object sender, EventArgs args) UpdateAdorner(null); } - /// - /// LayoutUpdated handler subscribed per adorned element. - /// Arms the layer-level dirty bit so the next OnLayoutUpdated fires UpdateAdorner. - /// - private void OnAdornedElementLayoutUpdated(object sender, EventArgs e) - { - _layoutDirty = true; - } - - /// - /// Subscribe to LayoutUpdated on the given element exactly once (tracked via - /// _subscribedElements). Called when the first adorner is registered for an element. - /// - private void SubscribeToElementLayout(UIElement element) - { - _subscribedElements ??= new HashSet(); - if (_subscribedElements.Add(element)) - { - element.LayoutUpdated += OnAdornedElementLayoutUpdated; - } - } - - /// - /// Unsubscribe from LayoutUpdated on the given element. - /// Called when the last adorner for an element is removed. - /// - private void UnsubscribeFromElementLayout(UIElement element) - { - if (_subscribedElements != null && _subscribedElements.Remove(element)) - { - element.LayoutUpdated -= OnAdornedElementLayoutUpdated; - } - } - /// /// Set the zOrder on the given adorner. /// @@ -739,7 +678,6 @@ internal void SetAdornerZOrder(Adorner adorner, int zOrder) adornerInfo.ZOrder = zOrder; AddAdornerToVisualTree(adornerInfo, zOrder); InvalidateAdorner(adornerInfo); - _layoutDirty = true; UpdateAdorner(adorner.AdornedElement); } @@ -1264,15 +1202,6 @@ private GeneralTransform GetProposedTransform(Adorner adorner, GeneralTransform // re-enter this same AdornerLayer cannot clobber an outer pass's snapshot. private object[] _zOrderValuesSnapshotBuffer; - // Dirty-bit gate for OnLayoutUpdated. Set on adorner add/remove and on any - // per-element LayoutUpdated event; cleared at the top of UpdateAdorner so a - // re-entrant fire during the walk re-arms for the next pass. - // Starts true so the very first layout pass is never skipped. - private bool _layoutDirty = true; - // Set of elements for which we have a LayoutUpdated subscription. - // Maintained to ensure subscribe/unsubscribe are balanced. - private HashSet _subscribedElements; - #endregion Private Fields } }