I maintain an old Swing application that uses custom border implementations as window decorations with a look and feel
implementation which extends MetalLookAndFeel. The look and feel
overrides initComponentDefaults(UIDefaults table)
and installs the custom border as "RootPane.frameBorder"
and so on.
The custom border itself draws 32 lines of predefined colors horizontally together by implementing paintBorder(Component c, Graphics g, int x, int y, int width, int height)
. This works fine with Windows 10 2004 or newer when the display scaling
is set to 100% in Settings > Display > Scale and layout
. When display scaling
is set to 125% or more, the border will not be drawn correctly, it has white lines between the lines that should be drawn together.
Fix scaling for apps
in Advanced scaling settings
seems to not affect the border drawing.
I use AdoptOpenJDK OpenJDK 11.0.4+11 x64
. I expect Swing to scale the lines up too, like fonts, etc.
Why this is happening? How this might be fixed?
The border implementation:
public class CustomWindowBorder implements Border{
public static final int BORDER_THICKNESS = 3;
private static final CustomWindowBorder globalInstance = new CustomWindowBorder();
public static CustomWindowBorder getInstance(){
return CustomWindowBorder.globalInstance;
}
private CustomWindowBorder(){}
@Override
public Insets getBorderInsets(Component c){
return new Insets(1,
CustomWindowBorder.BORDER_THICKNESS,
CustomWindowBorder.BORDER_THICKNESS,
CustomWindowBorder.BORDER_THICKNESS);
}
@Override
public boolean isBorderOpaque(){
return true;
}
@Override
public void paintBorder(Component c,
Graphics g,
int x,
int y,
int width,
int height){
Color[] decorationColors = [ommited]; // 30 colors in total
Color[] borderColors = [ommited]; // 3 colors in total
int yStart = 30;
for(int i = 0; i < decorationColors.length; i++ ){
// top
Color clr = decorationColors[i];
g.setColor(clr);
g.drawLine(0,
i,
width,
i);
}
for(int i = 0; i < borderColors.length; i++ ){
Color clr = borderColors[i];
g.setColor(clr);
// left
g.drawLine(i,
yStart,
i,
height - i - 1);
// right
g.drawLine(width - i - 1,
yStart,
width - i - 1,
height - i - 1);
// below
g.drawLine(i,
height - i - 1,
width - i - 1,
height - i - 1);
}
}
}
I found out that Swing draws exactly one un-scaled pixel wide lines on the screen when using Graphics::drawLine
or Graphics::fillRect
with one pixel width or height, independently from Windows's display scaling.
When I draw the border with Graphics::fillRect
and let it fill every 30 horizontal lines of the top and 3 lines on the other sides, the white space will be filled completely.
My solution is to draw each line over Graphics::fillRect
and fill the whole remaining lines with the color, reducing the width/height of the filled rectangle while moving it to the next line. It's not the most efficient way to draw this, but it works for me.
The modified paintBorder method:
@Override
public void paintBorder(Component c,
Graphics g,
int x,
int y,
int width,
int height){
Color[] decorationColors = [ommited]; // 30 colors in total
Color[] borderColors = [ommited]; // 3 colors in total
int yStart = 30;
for(int i = 0; i < decorationColors.length; i++ ){
// top
Color clr = decorationColors[i];
g.setColor(clr);
g.fillRect(0,
i,
width,
decorationColors.length - i);
}
for(int i = 0; i < borderColors.length; i++ ){
Color clr = borderColors ;
g.setColor(clr);
// left
g.fillRect(i,
yStart,
borderColors.length - i,
height - yStart - i);
}
for(int i = borderColors.length - 1; i >= 0; i-- ){
Color clr = borderColors[i];
g.setColor(clr);
// right
g.fillRect(width - i - 1,
yStart,
borderColors.length - i,
height - yStart - i);
// below
g.fillRect(i,
height - i - 1,
width - i,
borderColors.length - i);
}
}