@@ -67,11 +67,14 @@ public Rectangle Frame
6767 return ;
6868 }
6969
70- // BUGBUG: We set the internal fields here to avoid recursion. However, this means that
71- // BUGBUG: other logic in the property setters does not get executed. Specifically:
72- // BUGBUG: - Reset TextFormatter
73- // BUGBUG: - SetLayoutNeeded (not an issue as we explicitly call Layout below)
74- // BUGBUG: - If we add property change events for X/Y/Width/Height they will not be invoked
70+ // We set the internal fields directly (not via the X/Y/Width/Height setters) to avoid recursion.
71+ // This is intentional and the two side effects of the setters are accounted for elsewhere (#5498):
72+ // - TextFormatter constraints: recomputed by the Layout () -> SetRelativeLayout call below
73+ // (SetTextFormatterSize + FinalizeTextFormatterConstraints), so no reset is needed here.
74+ // - Width/Height (and X/Y) change events: deliberately NOT raised here. Those events observe
75+ // declarative Dim/Pos assignment; this path overwrites them with Absolute values as bookkeeping
76+ // to keep state consistent with an imperatively-set Frame. Use FrameChanged (raised by SetFrame
77+ // above) to observe resolved-size changes from any cause.
7578 // If Frame gets set, set all Pos/Dim to Absolute values.
7679 _x = _frame ! . Value . X ;
7780 _y = _frame ! . Value . Y ;
@@ -152,6 +155,14 @@ protected virtual void OnFrameChanged (in Rectangle frame) { }
152155 /// Raised when the <see cref="Frame"/> changes. This event is raised after the <see cref="Frame"/> has been
153156 /// updated.
154157 /// </summary>
158+ /// <remarks>
159+ /// This is the canonical event for observing resolved-size and position changes. It fires whenever the
160+ /// absolute <see cref="Frame"/> changes for any reason: assigning <see cref="X"/>, <see cref="Y"/>,
161+ /// <see cref="Width"/>, or <see cref="Height"/>; setting <see cref="Frame"/> directly; or a layout pass
162+ /// resolving a relative <see cref="Dim"/>/<see cref="Pos"/> (e.g. <see cref="DimFill"/>). By contrast, the
163+ /// <see cref="WidthChanged"/>/<see cref="HeightChanged"/> events observe assignment of the declarative
164+ /// <see cref="Width"/>/<see cref="Height"/> <see cref="Dim"/> only.
165+ /// </remarks>
155166 public event EventHandler < EventArgs < Rectangle > > ? FrameChanged ;
156167
157168 /// <summary>Gets the <see cref="Frame"/> with a screen-relative location.</summary>
@@ -368,6 +379,13 @@ public Pos Y
368379 /// allowing customization or cancellation of the change. The <see cref="HeightChanging"/> event
369380 /// is raised before the change, and <see cref="HeightChanged"/> is raised after.
370381 /// </para>
382+ /// <para>
383+ /// <see cref="HeightChanging"/>/<see cref="HeightChanged"/> observe assignment of this declarative
384+ /// <see cref="Dim"/> only. They are deliberately not raised when a layout pass resolves the view's size
385+ /// or when <see cref="Frame"/> is set directly, because those paths change the resolved
386+ /// <see cref="Frame"/> rather than the declarative <see cref="Height"/>. To observe resolved-size
387+ /// changes from any cause, subscribe to <see cref="FrameChanged"/>.
388+ /// </para>
371389 /// <para>The default value is <c>Dim.Absolute (0)</c>.</para>
372390 /// </remarks>
373391 /// <seealso cref="HeightChanging"/>
@@ -462,6 +480,13 @@ protected virtual void OnHeightChanged (ValueChangedEventArgs<Dim> args) { }
462480 /// allowing customization or cancellation of the change. The <see cref="WidthChanging"/> event
463481 /// is raised before the change, and <see cref="WidthChanged"/> is raised after.
464482 /// </para>
483+ /// <para>
484+ /// <see cref="WidthChanging"/>/<see cref="WidthChanged"/> observe assignment of this declarative
485+ /// <see cref="Dim"/> only. They are deliberately not raised when a layout pass resolves the view's size
486+ /// or when <see cref="Frame"/> is set directly, because those paths change the resolved
487+ /// <see cref="Frame"/> rather than the declarative <see cref="Width"/>. To observe resolved-size
488+ /// changes from any cause, subscribe to <see cref="FrameChanged"/>.
489+ /// </para>
465490 /// <para>The default value is <c>Dim.Absolute (0)</c>.</para>
466491 /// </remarks>
467492 /// <seealso cref="WidthChanging"/>
@@ -686,11 +711,14 @@ public bool SetRelativeLayout (Size superviewContentSize)
686711 _suppressNeedsDrawAfterLayout = false ;
687712 }
688713
689- // BUGBUG: We set the internal fields here to avoid recursion. However, this means that
690- // BUGBUG: other logic in the property setters does not get executed. Specifically:
691- // BUGBUG: - Reset TextFormatter
692- // BUGBUG: - SetLayoutNeeded (not an issue as we explicitly call Layout below)
693- // BUGBUG: - If we add property change events for X/Y/Width/Height they will not be invoked
714+ // We update the internal fields directly (not via the X/Y/Width/Height setters) to avoid recursion.
715+ // This is intentional and the two side effects of the setters are accounted for here (#5498):
716+ // - TextFormatter constraints: already recomputed by SetTextFormatterSize () above and finalized by
717+ // FinalizeTextFormatterConstraints () below, so no reset is needed here.
718+ // - Width/Height (and X/Y) change events: deliberately NOT raised. A layout pass resolves the Frame
719+ // without changing the declarative Dim/Pos (e.g. Dim.Fill () stays Dim.Fill ()); these assignments
720+ // only collapse an already-Absolute dim onto its resolved value. FrameChanged (raised by SetFrame
721+ // above) is the event for resolved-size changes.
694722 if ( _x is PosAbsolute )
695723 {
696724 _x = Frame . X ;
0 commit comments