最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

java - JTextArea has wrong number of columns - Stack Overflow

programmeradmin4浏览0评论

Extremely simple problem, as the title says: For a small project, I was trying to see how close I could get to a console look with a GUI application, and I did something like this as a test:

    public static void main(String[] args) {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        JTextArea text = new JTextArea(25, 80);
        text.setFont(new Font("Consolas", Font.PLAIN, 12));
        
        f.add(text);
        f.pack();
        f.setVisible(true);
    }

But, despite initializing the JTextArea with 80 columns, in the actual application, it fits only 75 characters:

The issue persists similarly with different numbers of columns, different fonts, and different font sizes, with the singular exception of Consolas size 14 somehow. The number of rows is always correct. The Java version I'm using is Java 17.

EDIT:
I have found at least a reason for these errors to happen. I had set my desktop to scale the size of text, apps etc. by 125%. If I set this to the standard 100%, then the text area does indeed fit 80 characters on a row exactly - or, it fits 80 without line wrap and 79 with line wrap, but I can simply increase its width by 1 pixel so it fits 80 characters in either case.
Obviously though, I don't want to change my screen settings (which Java evidently is not aware of) every time I run this one application, so I'd still be happy to hear an actual solution, if such a thing is possible.
Funny sidenote, after I changed my screen setting from 100% back to 125%, the text area is suddenly made to fit 87 characters instead of 80 or 75. But trying my actual code, not this minimal example, will still have 75. (Both after rebuilds.) Could it be that Java is doing some weird form of cross-execution cashing of window data?

Extremely simple problem, as the title says: For a small project, I was trying to see how close I could get to a console look with a GUI application, and I did something like this as a test:

    public static void main(String[] args) {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        JTextArea text = new JTextArea(25, 80);
        text.setFont(new Font("Consolas", Font.PLAIN, 12));
        
        f.add(text);
        f.pack();
        f.setVisible(true);
    }

But, despite initializing the JTextArea with 80 columns, in the actual application, it fits only 75 characters:

The issue persists similarly with different numbers of columns, different fonts, and different font sizes, with the singular exception of Consolas size 14 somehow. The number of rows is always correct. The Java version I'm using is Java 17.

EDIT:
I have found at least a reason for these errors to happen. I had set my desktop to scale the size of text, apps etc. by 125%. If I set this to the standard 100%, then the text area does indeed fit 80 characters on a row exactly - or, it fits 80 without line wrap and 79 with line wrap, but I can simply increase its width by 1 pixel so it fits 80 characters in either case.
Obviously though, I don't want to change my screen settings (which Java evidently is not aware of) every time I run this one application, so I'd still be happy to hear an actual solution, if such a thing is possible.
Funny sidenote, after I changed my screen setting from 100% back to 125%, the text area is suddenly made to fit 87 characters instead of 80 or 75. But trying my actual code, not this minimal example, will still have 75. (Both after rebuilds.) Could it be that Java is doing some weird form of cross-execution cashing of window data?

Share Improve this question edited Apr 1 at 20:08 Cecilia asked Apr 1 at 0:31 CeciliaCecilia 1451 silver badge5 bronze badges 7
  • Works for me with Java version 11 on Windows 11. So maybe a version/platform issue? Make sure you are using a monospaced font (maybe try "Monospaced") and make sure the Swing components are created on the Event Dispatch Thread by using SwingUtilities.invokeLater(). – camickr Commented Apr 1 at 1:41
  • The above code creates a window with 124 columns, and the text area would allow even more to go into one line. So it does not work on Ubuntu 24 LTS with OpenJDK 21. – queeg Commented Apr 1 at 5:11
  • 1 @camickr Consolas is a monospaced font. – Mark Rotteveel Commented Apr 1 at 9:52
  • 3 I can reproduce the behaviour (Windows 11), and if I comment out the font so it uses the default font, I get a wider screen (wide enough for about 124 columns). I guess there might be something off with font metrics or HiDPI calculations or something. – Mark Rotteveel Commented Apr 1 at 10:02
  • Pick a monospace font size. Adjust the JTextArea column number until you get 80 columns. It should be around 86 or 87. Simple problem solved. – Gilbert Le Blanc Commented Apr 1 at 12:54
 |  Show 2 more comments

1 Answer 1

Reset to default 5

The JTextArea calculates the preferred size by simply multiplying the column width with the specified number of columns. This property is an int value but the actual value is a fractional number. The font rendering will usually adjust certain features to physical pixels for a better appearance, so no differences will show up, as long as the unit of the column width matches physical pixels.

The problems start when screen scaling is used and thus, the unit used for the calculation is “logical pixels” which do not match the physical pixels.

When I run the following program

JFrame f = new JFrame();

JTextArea text = new JTextArea(25, 80);
text.setFont(new Font("Consolas", Font.PLAIN, 12));

f.add(text);
f.pack();
FontMetrics m = text.getFontMetrics(text.getFont());
System.out.println("m.charWidth('m'): " + m.charWidth('m')
    + ", m.getMaxAdvance(): " + m.getMaxAdvance()
    + ", actual: " + m.getMaxCharBounds(text.getGraphics()).getWidth());
System.out.print("getPreferredSize().width: "
    + text.getPreferredSize().width + ", ");
System.out.println("stringWidth(...): " + m.stringWidth("m".repeat(80)));

System.out.println();
f.dispose();

I get the following results:

100%
m.charWidth('m'): 7, m.getMaxAdvance(): 7, actual: 7.0
getPreferredSize().width: 560, stringWidth(...): 560
125%
m.charWidth('m'): 6, m.getMaxAdvance(): 7, actual: 6.400000095367432
getPreferredSize().width: 480, stringWidth(...): 512
150%
m.charWidth('m'): 7, m.getMaxAdvance(): 7, actual: 6.666666507720947
getPreferredSize().width: 560, stringWidth(...): 533
200%
m.charWidth('m'): 7, m.getMaxAdvance(): 7, actual: 6.5
getPreferredSize().width: 560, stringWidth(...): 520

We can see how the actual width differs despite given in the same logical unit, due to the fitting of the glyphs to the different grid of physical pixels. It’s also shown that everything looks fine when no scaling is active, your problem is reproduced with 125% scaling whilst using other scaling can reproduce the issue reported by others of having larger text areas than necessary.

Changing the font to Consolas 14 will indeed result in an integer number for the actual size in most environments. This might be just luck, due to the interaction of the actual glyph shapes and the grid of physical pixels but it’s also possible that the font has hints for this specific size, to better fit the glyphs to pixels.

If getColumnWidth() used getMaxAdvance() instead of m.charWidth('m') it would never become too small because getMaxAdvance() (almost) always rounds up, but it still could be to large and potentially way too large when using proportional fonts.

But for a fixed width font you could set the preferred size to ⟨max advance times number of columns⟩ for a quick fix. Or use a precise calculation using getMaxCharBounds, only rounding the end result.

Note that the component must be bound to the actual screen to get the right rendering attributes. This is especially important when you have multiple monitors with different scaling options and can’t derive the attributes from the default screen. That’s why the code above queries the values after the pack() call.

发布评论

评论列表(0)

  1. 暂无评论