@@ -96,6 +96,13 @@ public sealed partial class WindowPlacementManager : IDisposable
9696 /// <summary>Indicates whether the manager has been disposed to prevent further event handling.</summary>
9797 private bool _isDisposed ;
9898
99+ /// <summary>
100+ /// Snapshot of <c>IsMaximized</c> from the loaded saved state, captured before
101+ /// <see cref="CaptureOverlappedGeometry"/> can overwrite <see cref="_state"/>.
102+ /// Used by <see cref="WasMaximized"/> so the value is stable after <c>Activate()</c>.
103+ /// </summary>
104+ private readonly bool _loadedAsMaximized ;
105+
99106 // Win32 show-command constants supplementary to Win32Methods
100107
101108 /// <summary>SW_SHOWNORMAL – activate and show in original size/position.</summary>
@@ -116,6 +123,15 @@ public sealed partial class WindowPlacementManager : IDisposable
116123 /// </summary>
117124 public string ? Data => Serialize ( ) ;
118125
126+ /// <summary>
127+ /// <see langword="true" /> if the loaded saved state had the window maximized.
128+ /// The caller uses this to drive a post-<c>Activate</c> maximize so that
129+ /// <c>ExtendsContentIntoTitleBar</c> is already stable before the window goes maximized,
130+ /// avoiding the position jag that occurs when <c>SW_SHOWMAXIMIZED</c> is applied inside
131+ /// <c>WM_SHOWWINDOW</c> during <c>Activate()</c>.
132+ /// </summary>
133+ public bool WasMaximized => _loadedAsMaximized ;
134+
119135 /// <summary>
120136 /// <see langword="true" /> if the monitor layout encoded in the loaded data
121137 /// did not match the current monitor configuration – meaning the saved
@@ -156,6 +172,12 @@ public WindowPlacementManager(Window window, string? serialisedData)
156172 MonitorLayoutChanged = loadedState != null ; // true only when there WAS data
157173 }
158174
175+ // Capture the loaded maximized flag NOW, before CaptureOverlappedGeometry can
176+ // overwrite _state. WasMaximized reads this field; _state.IsMaximized is mutable
177+ // and will be set to false by CaptureOverlappedGeometry when ApplySavedPlacement
178+ // applies SW_SHOWNORMAL during Activate().
179+ _loadedAsMaximized = _state ? . IsMaximized == true ;
180+
159181 // WM_SHOWWINDOW fires before the first paint, so placement is applied
160182 // in one shot with no visible flicker.
161183 _monitor = new WindowMessageMonitor ( _window ) ;
@@ -196,6 +218,13 @@ private void ApplySavedPlacement()
196218 {
197219 if ( _state is not { } state ) return ;
198220
221+ // When the saved state is maximized, skip SetWindowPlacement entirely.
222+ // Changing window geometry inside WM_SHOWWINDOW (even with SW_SHOWNORMAL) while
223+ // ExtendsContentIntoTitleBar hasn't yet settled causes the same position jag as
224+ // the original SW_SHOWMAXIMIZED bug. The caller (ActivateForStartup) drives
225+ // Maximize() after Activate() returns, at which point the title bar is stable.
226+ if ( state . IsMaximized ) return ;
227+
199228 Win32Methods . GetWindowPlacement ( _hwnd , out var wp ) ;
200229 wp . length = ( uint ) Marshal . SizeOf < Win32Methods . WINDOWPLACEMENT > ( ) ;
201230
@@ -207,10 +236,7 @@ private void ApplySavedPlacement()
207236 Bottom = state . Y + state . Height
208237 } ;
209238
210- // Always restore as an overlapped window – never back into full-screen.
211- wp . showCmd = state . IsMaximized
212- ? Win32Methods . SW_SHOWMAXIMIZED
213- : SW_SHOWNORMAL ;
239+ wp . showCmd = SW_SHOWNORMAL ;
214240
215241 // Bracket SetWindowPlacement with the flag so that WM_DPICHANGED fired
216242 // by moving the window to a different-DPI monitor is suppressed above.
0 commit comments