Skip to content

Bring the main window to the front when reopening a collection (BL-16784) - #8268

Open
StephenMcConnel wants to merge 2 commits into
Version6.5from
BL-16784-BloomRestartsBehindChrome
Open

Bring the main window to the front when reopening a collection (BL-16784)#8268
StephenMcConnel wants to merge 2 commits into
Version6.5from
BL-16784-BloomRestartsBehindChrome

Conversation

@StephenMcConnel

@StephenMcConnel StephenMcConnel commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Switching collections doesn't restart Bloom — it closes the Shell and opens a new one in the same process. The only code that forces the main window to the front, Shell.ReallyComeToFront(), was reached only through the one-shot StartupScreenManager.DoLastOfAllAfterClosingSplashScreen, which is consumed when the splash screen closes at first startup. So on a reopen nothing brought the new window forward: it had only Show()'s implicit activation, which Windows refuses once another application (Chrome, say) took the foreground as Bloom's previous window closed. The window was displayed behind Chrome.

Why it looked new in 6.5 and only off the main monitor: the activation is a race, and Application.SetHighDpiMode(HighDpiMode.PerMonitorV2) (BL-16269) means Shell_Load setting saved bounds onto a different-DPI monitor now triggers a rescale before the window settles — enough extra work to lose a race it used to win.

The change

  • Program.OpenProjectWindow — after Show(), bring the new Shell forward when nothing else will, using the new StartupScreenManager.WillBringMainWindowToFrontWhenSplashCloses predicate. This is the one place every way of opening a collection funnels through, so it covers switching collections and the close/reopen after a UI-language or Collection Settings change. First startup is excluded, because the splash one-shot does it there and doing it twice would put the main window over the dialogs startup puts up.
  • Extensions — extracted BringToFrontNow from the BL-16690 BringToFrontWhenShown, so both share the topmost-then-drop idiom.
  • Shell.ReallyComeToFront — uses it, instead of the instant TopMost toggle that BL-16690 already documented as losing this race.
  • ProcessExtra.ForceWindowToForeground — being topmost is not enough on its own: when another application holds the foreground Windows refuses our Activate(), so the raised window is still not the active one and dropping topmost lands us behind it again. Measured: with only the topmost toggle, Bloom came up second, directly behind Chrome. This briefly attaches our input queue to the foreground window's thread, the standard way to be allowed to take the foreground.

Also fixed as a consequence: Shell._finishedLoading is set only in ReallyComeToFront, and Shell_ResizeEnd won't save window bounds until it is true — so after a collection switch Bloom silently stopped saving its window size and position.

Testing

Two tests in StartupScreenManagerTests cover the predicate the fix hangs on: that CloseSplashScreen consumes the one-shot (so a reopen answers "nobody will do this"), and that HideSplashScreenForDialog deliberately does not (so the startup route that shows the collection chooser still answers "no need").

Verified by hand on a two-monitor setup with Bloom and Chrome both maximized on the secondary monitor, switching collections:

  • before the fix: Bloom's window ended up below Chrome, not even in the top twelve of the z-order;
  • after: Chrome foreground before the switch, Bloom rank 0 and foreground after.

Ref: https://issues.bloomlibrary.org/youtrack/issue/BL-16784

Devin review


This change is Reviewable

StephenMcConnel and others added 2 commits August 31, 2026 14:24
…784)

Switching collections does not restart Bloom: it closes the Shell and opens a
new one in the same process. The only code that forces the main window to the
front, Shell.ReallyComeToFront, was reached only through the one-shot
StartupScreenManager.DoLastOfAllAfterClosingSplashScreen, which is consumed
when the splash screen closes at first startup. So on a reopen nothing brought
the new window forward: it had only Show()'s implicit activation, which Windows
refuses once another application (Chrome, say) took the foreground as Bloom's
previous window closed. Bloom came up invisible behind it.

OpenProjectWindow is the one place every way of opening a collection funnels
through, so the fix goes there, gated on a new named predicate,
WillBringMainWindowToFrontWhenSplashCloses: bring the window forward whenever
nothing else is going to. That covers switching collections and the close and
reopen after a UI language or Collection Settings change. First startup is
excluded, where the one-shot already does it and doing it twice would put the
main window over the dialogs startup puts up.

Being topmost is not enough by itself. When another application holds the
foreground, Windows refuses our Activate(), so the window we raised is not the
active one, and dropping topmost lands us behind it again -- measurably: with
only the topmost toggle Bloom came up second, directly behind Chrome. So
ProcessExtra.ForceWindowToForeground briefly attaches our input queue to the
foreground window's thread, the standard way to be allowed the foreground.

Along the way, BringToFrontNow is factored out of BringToFrontWhenShown (added
for BL-16690) so Shell.ReallyComeToFront shares the topmost-then-drop idiom
instead of the instant toggle that BL-16690 already documented as losing this
race.

Also fixed as a consequence: _finishedLoading is set only in ReallyComeToFront,
and Shell_ResizeEnd will not save window bounds until it is true, so after a
collection switch Bloom silently stopped saving its window size and position.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR makes newly reopened collection windows reliably become active and foreground on Windows while preserving the existing splash-screen startup behavior.

  • Adds a reusable delayed TopMost/foreground helper and uses it for Shell activation and existing dialog activation.
  • Tracks whether splash closure will perform the initial foreground action, allowing later collection opens to do it directly.
  • Adds native foreground-window handling and tests for splash one-shot lifecycle behavior.

Important Files Changed

Filename Overview
src/BloomExe/Program.cs Adds the foreground action after opening a collection when the startup splash one-shot will not perform it.
src/BloomExe/Extensions.cs Extracts the shared delayed TopMost activation behavior and adds explicit Windows foreground activation.
src/BloomExe/ToPalaso/ProcessExtra.cs Adds guarded foreground-window activation using temporary input-thread attachment with cleanup in a finally block.
src/BloomExe/MiscUI/StartupScreenManager.cs Exposes whether the splash-close foreground one-shot remains pending.
src/BloomExe/Shell.cs Reuses the new foreground helper while retaining the loading-complete state transition.
src/BloomTests/MiscUI/StartupScreenManagerTests.cs Verifies that closing the splash consumes the foreground one-shot while temporarily hiding it for a dialog does not.

Reviews (1): Last reviewed commit: "Merge remote-tracking branch 'origin/Ver..." | Re-trigger Greptile

@StephenMcConnel

Copy link
Copy Markdown
Contributor Author

[Claude Opus 5 (1M context)] Consulted Devin on 2026-08-31 20:31 UTC up to commit 1afdf537e077e08d34fbb46563d188250fb5dd65.

Devin's review completed for this commit with nothing to report: no bugs, no Investigate flags, and no Informational items. Greptile posted a summary with no findings, and both PR checks passed.

@StephenMcConnel
StephenMcConnel marked this pull request as ready for review August 31, 2026 21:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant