Minimum Size Scaling for Swing Windows

Since Java SE 9, Java AWT/Swing has supported automatic high DPI scaling on Windows and Linux, just like JavaFX. Ironically, this came with a bug that JavaFX used to have until Java 9: Window.setMinimumSize fails to scale the specified width and height on high DPI systems. (Unlike JavaFX, AWT/Swing windows ignore setMaximumSize anyway. Non-window components appear to scale both properties correctly, as far as I can tell. Lastly, I’m still on Java 10 so possibly this bug has already been fixed in Java 11 or 12.)

Windows do scale their current width and height, though. So a simple workaround is to listen to resizing events and restore some desired minimum size whenever the current size drops below it. The only problem is that such events cannot be canceled. AWT will first allow the user to visibly shrink the window below the minimum size before we get a chance to react and restore the minimum size, resulting in some ugly flickering. Still, that’s the best we can do until Window.setMinimumSize is fixed.

The ResizeListener class below implements this workaround. Add an instance to your window construction code via addComponentListener(new ResizeListener(this)) just before calling setVisible(true). This will use the current window size determined at this point as the minimum size. Alternatively, you can specify a minimum width and height explicitly.


import java.awt.*;
import java.awt.event.*;

/**
 * Prevents resizing a {@link Component} below its initial size.
 * Works around {@link Window#setMinimumSize(Dimension)} not respecting high DPI scaling.
 * @author Christoph Nahr
 */
public class ResizeListener extends ComponentAdapter {

    private final Component _component;
    private final int _width, _height;

    /**
     * Creates a {@link ResizeListener} for the specified {@link Component}.
     * The minimum width and height are set to the current width and height
     * of the specified {@code component}.
     *
     * @param component the {@link Component} whose resize events to track
     * @throws NullPointerException if {@code component} is {@code null}
     */
    public ResizeListener(Component component) {
        _component = component;
        _width = component.getWidth();
        _height = component.getHeight();
    }

    /**
     * Creates a {@link ResizeListener} for the specified {@link Component}
     * with the specified minimum width and height.
     *
     * @param component the {@link Component} whose resize events to track
     * @param width the minimum width for {@code component}
     * @param height the minimum height for {@code component}
     * @throws NullPointerException if {@code component} is {@code null}
     */
    public ResizeListener(Component component, int width, int height) {
        if (component == null)
            throw new NullPointerException("component");

        _component = component;
        _width = width;
        _height = height;
    }

    /**
     * Invoked when the size of the {@link Component} changes.
     * Restores the minimum size obtained during construction if
     * the current size is smaller in either or both dimensions.
     *
     * @param e the {@link ComponentEvent} that occurred
     */
    @Override
    public void componentResized(ComponentEvent e) {
        final int width = _component.getWidth();
        final int height = _component.getHeight();
        if (width < _width || height < _height)
            _component.setSize(Math.max(width, _width), Math.max(height, _height));
    }
}

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.