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:
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:
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)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With