Repository navigation
WizardDialog resizes itself in an endless loop after a DPI change #2986
Description
What happens
Dialog#hookZoomChangeListenerForDefaultSizedDialogs() installs an SWT.Resize listener that puts
the shell back to its computed size:
Listener adaptDefaultSizeListener = event -> {
Shell shell = getShell();
if (shell == null || shell.isDisposed()) {
return;
}
Point size = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
shell.setSize(size); // Dialog.java:1118
};setSize fires the next SWT.Resize, which calls the listener again. There is no re-entrancy guard
and no check that the size actually changed, so whenever computeSize(SWT.DEFAULT, SWT.DEFAULT, true)
is not a fixed point the listener recurses until the stack is exhausted.
Two symptoms, both reported by users:
- The dialog widens step by step with visible paint artefacts, the window stops responding, and the
log getsjava.lang.StackOverflowErrorunder "Unhandled event loop exception". - Once the listener is armed, the dialog can no longer be resized with the mouse at all - every drag
of a border is reset to the computed size on the nextSWT.Resize.
Why wizards are affected
The hook returns early for resizable dialogs:
if (resizeHasOccurred || hasCustomSize || isEmpty || isResizable()) { // Dialog.java:1107
return;
}WizardDialog builds its shell with SWT.RESIZE:
setShellStyle(SWT.CLOSE | SWT.MAX | SWT.TITLE | SWT.BORDER | SWT.RESIZE | getShellModality(modal)
| getDefaultOrientation()); // WizardDialog.java:312but it never overrides Dialog#isResizable(), so it inherits the false from Dialog.java:1358.
JFace therefore treats every wizard as a fixed-size dialog and hooks the listener.
The other two early-outs do not help either: WizardDialog#showStartingPage() deliberately skips the
size update before initializeBounds() ("we do not need to update the size since the call to
initialize bounds has not been made yet", WizardDialog.java:1276-1277), so resizeHasOccurred and
hasCustomSize stay false.
Steps to reproduce
The attached WizardResizeLoop.java is a standalone SWT/JFace program.
Single monitor. Run it with no arguments. It fires SWT.ZoomChanged on the shell's first child,
which is what Composite#handleDPIChange() delivers after WM_DPICHANGED, and is the event the
JFace hook listens for. The dialog then refuses every mouse resize, and widening it far enough makes
it loop.
Two monitors. Set different Windows scaling factors on them, e.g. 100% and 175%, and run the
program with the argument dpi. Drag the dialog from one monitor to the other.
Where it was seen
A product built on 2025-12 (org.eclipse.jface 3.38.100.v20251108-1551,
org.eclipse.swt.win32.win32.x86_64 3.132.0.v20251124-0642, Windows 11, Java 21.0.3). Two machines,
each a laptop plus an external monitor with a different scaling factor, produced byte-identical
1025-line stacks - 64 iterations of
org.eclipse.jface.dialogs.Dialog.lambda$1(Dialog.java:1118)
org.eclipse.swt.widgets.Control.setSize(Control.java:3857)
org.eclipse.swt.widgets.Shell.setBoundsInPixels(Shell.java:1636)
org.eclipse.swt.widgets.Composite.sendResize(Composite.java:1016)
org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1217)
Dialog#hookZoomChangeListenerForDefaultSizedDialogs() is unchanged in master as of 2026-10-02.
Monitor-specific scaling is a precondition, and it is on by default:
Workbench#createDisplay() sets swt.autoScale.updateOnRuntime from the configuration-scope
preference org.eclipse.ui.workbench/monitorSpecificScaling, whose default is true. Switching
"Use monitor-specific UI scaling" off in General > Appearance avoids the whole thing.
Proposed change
Two independent parts, both in the attached patch:
-
WizardDialogshould overrideisResizable()to report what its shell style says:@Override protected boolean isResizable() { return (getShellStyle() & SWT.RESIZE) != 0; }
Reading the flag rather than returning
truekeeps callers working that stripSWT.RESIZE,
whichWizardDialog#setShellStyledocuments as supported (WizardDialog.java:327-328). -
adaptDefaultSizeListenerskips a nested call and skips a size the shell already has. Without
that, any dialog whosecomputeSize(...)is not a fixed point can still loop - the defect is not
limited to wizards.AtomicBoolean adaptingDefaultSize = new AtomicBoolean(); Listener adaptDefaultSizeListener = event -> { Shell shell = getShell(); if (shell == null || shell.isDisposed() || !adaptingDefaultSize.compareAndSet(false, true)) { return; } try { Point size = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); if (!size.equals(shell.getSize())) { shell.setSize(size); } } finally { adaptingDefaultSize.set(false); } };
The whole org.eclipse.jface bundle compiles with both parts.
Attachment
WizardResizeLoop.java
Activity
astrograph commentedon Oct 2, 2026
see eclipse-platform/eclipse.platform.ui#4428
WizardDialog resizes itself in an endless loop after a DPI change ecl…
WizardDialog resizes itself in an endless loop after a DPI change ecl…
WizardDialog resizes itself in an endless loop after a DPI change ecl…
WizardDialog resizes itself in an endless loop after a DPI change ecl…