如何计算文本在JTextArea中的行数(以及每行中的列数)?

时间:2011-05-12 14:44:03

标签: java swing jtextarea

question中提出的问题感兴趣后 我试图接近它几次并且失败了,我不喜欢那样:)

我认为如果将问题分解为子问题,可能有助于解决问题。

为简单起见,假设JTextArea不会改变其大小,因此我们不需要担心重新评估等。我认为重要的问题是:

1.如何计算某个文本在JTextArea中占用的行数?

2. JTextArea中的列数与行中可以容纳的字符数之间的关系是什么?所以我们可以计算行长度。

请在下面找到包含要处理的文本区域的示例代码:

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

public class TextAreaLines
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                JPanel p = new JPanel();
                JFrame f = new JFrame();
                JTextArea ta = new JTextArea("dadsad sasdasdasdasdasd");
                ta.setWrapStyleWord(true);
                ta.setLineWrap(true);
                ta.setRows(5);
                ta.setColumns(5);
                p.add(ta);
                f.setContentPane(p);
                f.setSize(400, 300);
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setVisible(true);             
                //BTW the code below prints 1
                System.out.println("ta.getLineCount()="+ta.getLineCount());
            }
        });
    }
}

EDIT1:所以我提出了以下代码,但问题是输出不是你看到的,即

//for input
//JTextArea ta = new JTextArea("alfred abcdefghijklmnoprstuwvxyz abcdefg");

//we have output    
//s=alfred abcdefghijk
//s=lmnoprstuwvxyz a
//s=bcdefg



        FontMetrics fm = ta.getFontMetrics(ta.getFont());
        String text = ta.getText();
        List<String> texts = new ArrayList<String>();               
        String line = "";
        //no word wrap
        for(int i = 0;i < text.length(); i++)  
        {       
            char c = text.charAt(i);
            if(fm.stringWidth(line +c)  <= ta.getPreferredSize().width)
            {                       
                //System.out.println("in; line+c ="+(line + c));
                line += c;
            }
            else
            {
                texts.add(line);//store the text
                line = ""+c;//empty the line, add the last char
            }
        }
        texts.add(line);                
        for(String s: texts)
            System.out.println("s="+s);

我做错了什么,我忘记了什么?文本区域没有自动换行。

EDIT2 :@trashgod这是我得到的输出。从中可以看出,我们有不同的默认字体。实际上问题可能是字体甚至是系统依赖的。 (PS:我在Win7上)。

line: Twas brillig and the slithy tovesD
line: id gyre and gimble in the wabe;
line count: 2
preferred: java.awt.Dimension[width=179,height=48]
bounds1: java.awt.geom.Rectangle2D$Float[x=0.0,y=-12.064453,w=170.0,h=15.09375]
layout1: java.awt.geom.Rectangle2D$Float[x=0.28125,y=-8.59375,w=168.25,h=11.125]
bounds2: java.awt.geom.Rectangle2D$Float[x=0.0,y=-12.064453,w=179.0,h=15.09375]
layout2: java.awt.geom.Rectangle2D$Float[x=0.921875,y=-8.59375,w=177.34375,h=11.125]

在我的头脑中编译你们所有人都说的话我认为可能可靠的解决方案可能是破解文本区域设置文本的方式,并充分利用控制它。通过运行算法(请注意,正如@trashgod所建议的那样,'&lt;'被改为'&lt; =')在该区域的setText中。

是什么让我这样思考...例如,如果你提供我提供的样本 将textarea的文本更改为JTextArea ta = new JTextArea("alfred abcdefghijkl\nmnoprstuwvxyz ab\ncdefg");,因为它是根据我的情况计算的,然后它将完全适合textarea。

EDIT3:这是我快速入侵的一种解决方案,至少现在显示的字符与计算完全相同。其他人可以检查一下,让我知道,可能它在其他机器上如何工作,然后Win7?下面的示例已准备好使用您应该能够调整窗口大小并获得与您看到的相同的行的打印输出。

import java.awt.BorderLayout;
import java.awt.FontMetrics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

public class TextAreaLines
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                JPanel p = new JPanel(new BorderLayout());
                JFrame f = new JFrame();
                final JTextArea ta = new JTextArea("alfred abcdefghijklmnoprstuwvxyz abcdefg");
                ta.addComponentListener(new ComponentAdapter()
                {
                    @Override
                    public void componentResized(ComponentEvent e)
                    {
                        super.componentResized(e);
                        System.out.println("ta componentResized");
                        reformatTextAreaText(ta);
                    }
                });
                //ta.setWrapStyleWord(true);
                ta.setLineWrap(true);
                p.add(ta);
                f.setContentPane(p);
                f.setSize(200, 100);
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setVisible(true);
            }

            private void reformatTextAreaText(JTextArea ta)
            {
                String text = ta.getText();
                //remove all new line characters since we want to control line braking
                text = text.replaceAll("\n", "");
                FontMetrics fm = ta.getFontMetrics(ta.getFont());
                List<String> texts = new ArrayList<String>();
                String line = "";
                //no word wrap
                for(int i = 0; i < text.length(); i++)
                {
                    char c = text.charAt(i);
                    if(fm.stringWidth(line + c) <= ta.getPreferredSize().width)
                    {
                        //System.out.println("in; line+c ="+(line + c));
                        line += c;
                    }
                    else
                    {
                        texts.add(line);//store the text
                        line = "" + c;//empty the line, add the last char
                    }
                }
                texts.add(line);
                //print out of the lines
                for(String s : texts)
                    System.out.println("s=" + s);
                //build newText for the
                String newText = "";
                for(String s : texts)
                    newText += s + "\n";
                ta.setText(newText);
            }
        });
    }
}

提前致谢。

7 个答案:

答案 0 :(得分:10)

  

我做错了什么,我忘记了什么?

没什么,真的。我修改了您的示例以在宽度上使用“&lt; =”并突出显示一些功能:

  1. FontMetrics注意到,“String的进展不一定是孤立地测量其角色的进展的总和......”

  2. 文本组件的首选大小与最宽的行相匹配。由于比例间距的原因,这会因字体而异。

  3. TextLayout显示更严格的界限,但请注意“基线相对坐标。”

  4. getLineCount()方法计算line.separator分隔线,而非包裹线。

  5. line: Twas brillig and the slithy toves
    line: Did gyre and gimble in the wabe;
    line count: 2
    preferred: java.awt.Dimension[width=207,height=48]
    bounds1: java.awt.geom.Rectangle2D$Float[x=0.0,y=-12.568359,w=205.0,h=15.310547]
    layout1: java.awt.geom.Rectangle2D$Float[x=0.0,y=-10.0,w=200.0,h=13.0]
    bounds2: java.awt.geom.Rectangle2D$Float[x=0.0,y=-12.568359,w=207.0,h=15.310547]
    layout2: java.awt.geom.Rectangle2D$Float[x=1.0,y=-10.0,w=205.0,h=13.0]
    
    import java.awt.Dimension;
    import java.awt.FontMetrics;
    import java.awt.font.FontRenderContext;
    import java.awt.font.TextLayout;
    import java.util.ArrayList;
    import java.util.List;
    import javax.swing.JFrame;
    import javax.swing.JTextArea;
    import javax.swing.SwingUtilities;
    
    /** #see http://stackoverflow.com/questions/5979795 */
    public class TextAreaLine {
    
        private static final String text1 =
            "Twas brillig and the slithy toves\n";
        private static final String text2 =
            "Did gyre and gimble in the wabe;";
        private static final JTextArea ta = new JTextArea(text1 + text2);
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
    
                @Override
                public void run() {
                    display();
                }
            });
        }
    
        static void display() {
            JFrame f = new JFrame();
            ta.setWrapStyleWord(false);
            ta.setLineWrap(false);
            ta.setRows(3);
            f.add(ta);
            f.pack();
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setLocationRelativeTo(null);
            f.setVisible(true);
            FontMetrics fm = ta.getFontMetrics(ta.getFont());
            List<String> texts = new ArrayList<String>();
            Dimension d = ta.getPreferredSize();
            String text = ta.getText();
            String line = "";
            for (int i = 0; i < text.length(); i++) {
                char c = text.charAt(i);
                if (c != '\n') {
                    if (fm.stringWidth(line + c) <= d.width) {
                        line += c;
                    } else {
                        texts.add(line);
                        line = "" + c;
                    }
                }
            }
            texts.add(line);
            for (String s : texts) {
                System.out.println("line: " + s);
            }
            System.out.println("line count: " + ta.getLineCount());
            System.out.println("preferred: " + d);
            System.out.println("bounds1: " + fm.getStringBounds(text1, null));
            FontRenderContext frc = new FontRenderContext(null, false, false);
            TextLayout layout = new TextLayout(text1, ta.getFont(), frc);
            System.out.println("layout1: " + layout.getBounds());
            System.out.println("bounds2: " + fm.getStringBounds(text2, null));
            layout = new TextLayout(text2, ta.getFont(), frc);
            System.out.println("layout2: " + layout.getBounds());
        }
    }
    

答案 1 :(得分:5)

您可以做的一件事就是使用FontMetrics。我写了一些代码,用于在某些行号上分割JTextArea。设置代码如下所示:

Graphics2D g = (Graphics2D) g2;
FontMetrics m = g.getFontMetrics();
int lineHeight = m.getHeight();

这将告诉你文本行的高度。

不幸的是,大多数字体的字母宽度都不同。但是,您可以使用以下代码来确定String的宽度。

int width = m.getStringBounds("Some String", g).getWidth();

我知道这并没有完全回答你的问题,但我希望它有所帮助。

如果你没有使用自动换行,这里是你可以使用的通用算法:(在paint组件方法中)

String text[] = getText().split("\n");
String newText = "";
for (String line: text) {
    newText = line + "| " + line.length() + "\n";
}
setText(newText);

这是一般的想法。不确定它会有多好。如果您尝试,请告诉我。

答案 2 :(得分:5)

不确定这是否有帮助,但您需要设置文本区域的宽度,以便视图知道何时包装文本。设置尺寸后,您可以确定首选高度。当您知道首选高度时,您可以使用字体metrice行高来确定包括包裹行(如果有)的总行数。

import java.awt.*;
import javax.swing.*;

public class TextAreaPreferredHeight extends JFrame
{

    public TextAreaPreferredHeight()
    {
        JTextArea textArea = new JTextArea();
        textArea.setText("one two three four five six seven eight nine ten");
        textArea.setLineWrap( true );
        textArea.setWrapStyleWord( true );

        FontMetrics fm = textArea.getFontMetrics( textArea.getFont() );
        int height = fm.getHeight();

        System.out.println("000: " + textArea.getPreferredSize());
        textArea.setSize(100, 1);
        System.out.println("100: " + textArea.getPreferredSize());
        System.out.println("lines : " + textArea.getPreferredSize().height / height);

        textArea.setSize(200, 1);
        System.out.println("200: " + textArea.getPreferredSize());
        System.out.println("lines : " + textArea.getPreferredSize().height / height);

        textArea.setSize(300, 1);
        System.out.println("300: " + textArea.getPreferredSize());
        System.out.println("lines : " + textArea.getPreferredSize().height / height);
        add(textArea);
        pack();
        setVisible(true);
}

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

答案 3 :(得分:3)

我见过有人使用TextLayout来做这样的事情。

答案 4 :(得分:2)

好的,我编写了一个可以加载到图像中的程序,它会将其转换为ascii art。我希望它根据输入的图像自动使文本区域成为正确的宽高比。

然而,我永远无法让它正常工作。我放弃并评论了我的尝试,这是我尝试过的片段。我最后做的只是有一个textarea,有时候没有完全填满。

                //Graphics fontG = this.textBox4.CreateGraphics();
                //fontG.PageUnit = GraphicsUnit.Point;
                //SizeF fontSize = fontG.MeasureString(RowData, this.textBox4.Font,(SizeF) this.textBox4.ClientSize);
                sb.AppendLine();
                RowData = "";
                //fontH +=  fontSize.Height + 1.2F;
                //fontW = (int) fontSize.Width;

答案 5 :(得分:2)

昨天有一个类似的问题。我认为需要解决的方法是通过迭代方法:

  • 获取JTextArea width
  • 使用FontMetrics获取字符串的宽度,如jjnguy建议的那样
  • 用文字分割你的字符串。
  • 开始测量一个字符串的字符,一次添加一个字,直到达到区域宽度。保存这个号码。
  • 一旦到达它,开始一个新的迭代,一次添加一个单词(从保存的数字开始)。
  • 行数将是迭代次数。

不幸的是,行长度取决于字体和特定字符串(并非每个字符都是相同的宽度)。您可以计算每次迭代中单词中的字符数,并返回长度数组或长度平均值。

这是相关的Android问题:How to find android TextView number of characters per line?

答案 6 :(得分:2)

我发现通过文本分析计算行数只会导致一无所获。相反,我的解决方案是从文本区域的首选大小计算它...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;

public class LineNumbering extends JFrame {

    private static final long serialVersionUID = 1L;
    private static final Font fixedFont = new Font("Monospaced", Font.PLAIN, 12);
    private static JTextArea jta;
    private static JTextArea lines;
    private static String lineSeparator = "\n";
    private static int numRows = 10;
    private static int numCols = 30;

    public LineNumbering() {
        super("Line Numbering Example");
    }

    public static void createAndShowGUI() {
        JFrame frame = new LineNumbering();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JScrollPane jsp = new JScrollPane();
        jta = new JTextArea(numRows, numCols);
        jta.setFont(fixedFont);
        jta.setLineWrap(true);
        jta.setWrapStyleWord(true);

        lines = new JTextArea(numRows, 3);
        lines.setEditable(false);
        lines.setFocusable(false);
        lines.setEnabled(false);
        lines.setFont(fixedFont);
        lines.setBackground(Color.LIGHT_GRAY);
        lines.setDisabledTextColor(Color.BLACK);
        // do initial line numbering
        for (int i = 1; i <= lines.getRows(); i++) {
            lines.append(i + System.getProperty("line.separator"));
        }

        final class DebugCaretListener implements CaretListener {

            int rowHeight = jta.getFontMetrics(jta.getFont()).getHeight();

            /**
             * @return total of lines showed in the text area
             */
            private int getTotalLinesInView() {
                int insetsTotalHeight = jta.getInsets().top + jta.getInsets().bottom;
                return (jta.getPreferredSize().height - insetsTotalHeight) / rowHeight;
            }

            /**
             * @return text with line numbers
             */
            public String getText() {
                StringBuffer text = new StringBuffer();
                int totalLines = getTotalLinesInView();
                System.out.println("totalLines : " + totalLines);
                for (int i = 1; i <= totalLines; i++) {
                    text.append(i);
                    if (i < totalLines) {
                        text.append(lineSeparator);
                    }
                }
                return text.toString();
            }

            /**
             * <p>
             * Reset line numbers on caret event. Since the total number of
             * lines is calculated from preferred size of text area, we do this
             * on an event that occurred after repainting of the text area.
             * </p>
             * (non-Javadoc)
             * 
             * @see javax.swing.event.CaretListener#caretUpdate(javax.swing.event.CaretEvent)
             */
            @Override
            public void caretUpdate(CaretEvent e) {
                int totalLines = getTotalLinesInView();
                System.out.println("totalLines : " + totalLines);
                if (totalLines >= numRows) {
                    lines.setText(getText());
                }
            }

        }

        jta.addCaretListener(new DebugCaretListener());
        jsp.getViewport().add(jta);
        jsp.setRowHeaderView(lines);
        jsp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
        jsp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

        JPanel textPanel = new JPanel();
        textPanel.add(jsp);
        JPanel contentPanel = new JPanel();
        contentPanel.add(textPanel);
        frame.setContentPane(contentPanel);
        contentPanel.setOpaque(true);
        frame.pack();
        frame.setPreferredSize(new Dimension(500, 500));
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                createAndShowGUI();
            }
        });
    }
}