HiDPI support with java 9+, scalling issue with JTable gridlines with Windows L&F - but not Nimbus
Asked Answered
H

1

8

I'm migrating my Swing app to Java 11 to take advantage of the HiDPI display support. I'm using a Samsung monitor with resolution set to 3840x2160, scaling at 125%, with Windows 10.

Although java 9 and above are advertised as properly handling HiDPI scaling, when displaying a simple JTable, the gridlines appear of different thickness, as shown here:

JTable gridlines issue

Here's the code for this:

import javax.swing.*;

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

    public TestTable() {
        JTable table = new JTable(12,6);

        JDialog dialog = new JDialog();
        JScrollPane sp = new JScrollPane(table);

        table.setShowGrid(true);
        table.setRowHeight(25);

        dialog.setContentPane(sp);
        dialog.setSize(300,300);
        dialog.setVisible(true);
        dialog.setLocationRelativeTo(null);
    }
}

However, when setting the Nimbus L&F, the problem goes away:

JTable Nimbus

import javax.swing.*;

public class TestTable {
    public static void main(String[] args) {
        try {
            for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (Exception e) { }

        new TestTable();
    }

    public TestTable() {
        JTable table = new JTable(12,6);

        JDialog dialog = new JDialog();
        JScrollPane sp = new JScrollPane(table);

        table.setShowGrid(true);
        table.setRowHeight(25);

        dialog.setContentPane(sp);
        dialog.setSize(300,300);
        dialog.setVisible(true);
        dialog.setLocationRelativeTo(null);
    }
}

How can I achieve the same with the default Windows L&F ?

(Same behavior is observed with java 9 & 10)

Herrah answered 4/12, 2018 at 1:47 Comment(0)
P
6

The difference is how the two look and feels render their grid lines.

The default look and feel MetalLookAndFeel (and the WindowsLookAndFeel) is based around BasicLookAndFeel which uses the BasicTableUI class to render the JTable. In BasicTableUI.paintGrid() it calls such as SwingUtilities2.drawHLine() - which actually calls Graphics.fillRect() which is the problem.

The Nimbus look and feel uses the SynthTableUI class. In SynthTableUI.paintGrid() it does ultimately call Graphics.drawLine(), which clearly draws a cleaner line under scaling.

As you say, that sounds like a bug in the main look and feels under HiDPI.


It is possible to create a workaround for this, though it's not particularly elegant.

With a custom version of the Graphics that is being used, it's possible to override fillRect() to use drawLine() instead, if the width or height is 1. This custom Graphics can be introduced specifically when painting the table:

    JTable table = new JTable(12, 6) {
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(new GraphicsWorkaround(g));
        }
    };

(An anonymous subclass is just used for brevity).

Then the GraphicsWorkaround class is written as a wrapper to the true g that was passed in. Subclassing DebugGraphics here is just a trick to save having to write delegate calls in all the other methods in Graphics:

import java.awt.Graphics;
import javax.swing.DebugGraphics;

public class GraphicsWorkaround extends DebugGraphics {
    private final Graphics g;

    public GraphicsWorkaround(Graphics g) {
        super(g);
        this.g = g;
    }

    @Override
    public Graphics create() {
        return new GraphicsWorkaround(g.create());
    }

    @Override
    public void fillRect(int x, int y, int width, int height) {
        if (width == 1)
            g.drawLine(x, y, x, y + height - 1);
        else if (height == 1)
            g.drawLine(x, y, x + width - 1, y);
        else
            super.fillRect(x, y, width, height);
    }
}

(The create() method is there to handle the internal scratchGraphics clone created in JComponent.paintComponent() ).

This then enables drawLine() to be called after all, which looked much better at 125% scaling.

Perak answered 7/12, 2018 at 5:59 Comment(1)
Thanks, I will accept this answer as it clearly explains the underlying cause for this behavior, and propose a good workaround. It however creates a few other issues on my end (when expanding the original example), related to fonts & colors, but I believe I can find solutions for those. I guess I will report the behavior (bug ?) and see what happens.Herrah

© 2022 - 2024 — McMap. All rights reserved.