Java swing application too small in ~HiDpi~ computers

I have a java desktop application which uses java swing and it works fine with normal displays.

But when come to ~hiDpi~ displays( 3200*1800) whole application is too small.

As application code is very large and complex, it is difficult to rearrange code to match with hi dpi. Is there a solution to this problem?

I have seen application like IntelliJ idea and eclipse works fine with this kind of displays without a problem. Appreciate any help.


Some time ago, I was tasked with developing a solution which would allow a user to increase or decrease the font size of the application dynamically. Needless to say, I spent a lot of time tearing my hair out (mostly because my predecessor was instant on using setPreferredSize and other stupid ideas which made any solution a pain to implement)

The following is an example of the idea I came up with. It allows you to modify the UIManagers "font" properties, applying a scale to the font size.

Basically, it scans the UIManagers UIDefaults, picking out all the "font" based attributes and stores these as a "base". Once it's done that, it uses these values, calculates the font size, based on the scale and the original size, and updates the values in the UIManager

import java.awt.Font;
import java.util.HashMap;
import java.util.Map;
import javax.swing.UIManager;
import sun.swing.SwingLazyValue;

public class FontUtilities {

    private static Map<String, Font> originals;

    public static void setFontScale(float scale) {

        if (originals == null) {
            originals = new HashMap<>(25);
            for (Map.Entry entry : UIManager.getDefaults().entrySet()) {
                Object key = entry.getKey();
                if (key.toString().toLowerCase().contains(".font")) {
                    Object value = entry.getValue();
                    Font font = null;
                    if (value instanceof SwingLazyValue) {
                        SwingLazyValue lazy = (SwingLazyValue) entry.getValue();
                        value = lazy.createValue(UIManager.getDefaults());
                    }

                    if (value instanceof Font) {
                        font = (Font) value;
                        originals.put(key.toString(), font);
                    }
                }
            }
        }

        for (Map.Entry<String, Font> entry : originals.entrySet()) {
            String key = entry.getKey();
            Font font = entry.getValue();

            float size = font.getSize();
            size *= scale;

            font = font.deriveFont(Font.PLAIN, size);
            UIManager.put(key, font);
        }
    }

}

Font Scaling

The example is taking from the Swing tutorials, with the addition of the font scaling

Basically, when ever the + button is clicked, it's running this code...

scale += 1f;
FontUtilities.setFontScale(scale);
SwingUtilities.updateComponentTreeUI(TextSamplerDemo.this);
updateUI();
revalidate();
repaint();

The basic idea would be to define a scaling algorithm, based on the "default" screen resolution been 1 and as the screen resolution/DPI increases, you can increase the font scaling to follow.

There are problems with this. It might not work on look and feels (looking at you nimbus) and if you define your own fonts they won't be updated. It's also a really great way to see when you've done stupid things with the api, as it will play havoc with your layouts

The other solution would be to use JXLayer/JLayer to dynamically scale the UI as a whole.

JLayer Zooming

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.RenderingHints;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.jdesktop.jxlayer.JXLayer;
import org.pbjar.jxlayer.demo.TransformUtils;
import org.pbjar.jxlayer.plaf.ext.transform.DefaultTransformModel;

public class TestJLayerZoom {

    public static void main(String[] args) {
        new TestJLayerZoom();
    }

    public TestJLayerZoom() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private JXLayer<JComponent> layer;
        private DefaultTransformModel transformModel;
        private JPanel content;

        public TestPane() {

            content = new JPanel(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridy = 0;

            JLabel label = new JLabel("Hello");
            JTextField field = new JTextField("World", 20);

            content.add(label, gbc);
            content.add(field, gbc);

            gbc.gridy++;
            gbc.gridwidth = GridBagConstraints.REMAINDER;

            final JSlider slider = new JSlider(50, 200);
            slider.addChangeListener(new ChangeListener() {

                @Override
                public void stateChanged(ChangeEvent e) {
                    int value = slider.getValue();
                    double scale = value / 100d;
                    transformModel.setScale(scale);
                }
            });
            content.add(slider, gbc);

            transformModel = new DefaultTransformModel();
            transformModel.setScaleToPreferredSize(true);

            Map<RenderingHints.Key, Object> hints = new HashMap<>();
            //hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            //hints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
            //hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

            layer = TransformUtils.createTransformJXLayer(content, transformModel, hints);
            setLayout(new BorderLayout());
            add(layer);


        }

    }

}

This is based on the idea presented here. There is an additional library linked in the answer which you will need to make this work