Как рассчитать количество строк (и столбцов в каждой строке), которые текст принимает в 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: Итак, я придумал следующий код, но проблема в том, что результат не тот, который вы видите, i.e

//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, "<" было изменено на "< =" ) в setText области.

Что заставило меня так думать... например, в примере, который я представил, если вы измените текст текстового поля на JTextArea ta = new JTextArea("alfred abcdefghijkl\nmnoprstuwvxyz ab\ncdefg");, как он рассчитан в моем случае, тогда он отлично впишется в текстовое поле.

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);
            }
        });
    }
}

Спасибо заранее.

Ответ 1

Что я делаю неправильно, о чем я забываю?

Ничего, действительно. Я изменил ваш пример, чтобы использовать "< =" по ширине и выделить несколько функций:

  • FontMetrics отмечает, что "продвижение a String не обязательно является суммой достижений его персонажей, измеренных изолированно..."

  • Предпочтительный размер текстового компонента очень близок к метрическим границам для самой широкой строки. Это зависит от размера шрифта из-за пропорционального расстояния.

  • TextLayout показывает даже более жесткие границы, но обратите внимание на "базовые относительные координаты".

  • Метод getLineCount() подсчитывает строки line.separator с разделителями, а не завернутые строки.

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());
    }
}

Ответ 2

Одна вещь, которую вы можете сделать, это использовать FontMetrics. Я написал код для разбиения JTextArea на определенные номера строк. Код установки выглядел так:

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

Это расскажет вам, насколько высока строка текста.

К сожалению, буквы имеют разную ширину в большинстве шрифтов. Но для определения ширины строки можно использовать следующий код.

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

Я знаю, что это не полностью отвечает на ваш вопрос, но я надеюсь, что это поможет.

Если вы не используете перенос слов, вот общий алгоритм, который вы могли бы использовать: (в методе компонента краски)

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

Это общая идея. Не уверен, насколько хорошо это сработает. Дайте мне знать, если вы попробуете.

Ответ 3

Не уверен, что это помогает, но вам нужно установить ширину текстовой области, чтобы представление знало, когда нужно обернуть текст. Как только вы зададите размер, вы можете определить предпочтительную высоту. Когда вы знаете предпочтительную высоту, вы можете использовать высоту линии метрика шрифта, чтобы определить общее количество строк, включая завернутые строки, если они есть.

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();
    }
}

Ответ 4

Я видел людей, использующих TextLayout для чего-то вроде этого.

Ответ 5

Хорошо, я написал программу, которую вы могли бы загрузить в изображении, и она преобразует ее в искусство ascii. Я хотел, чтобы он автоматически превращал текстовую область в правильный аспектный рацион на основе введенного изображения.

Однако я не мог заставить его работать правильно. Я сдался и заметил мои попытки, вот фрагмент того, что я пытался. То, что я закончил, - это просто текстовое поле, которое иногда не заполнялось полностью.

                //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;

Ответ 6

Вчера у андроида был аналогичный вопрос. Способ, которым я его вижу, должен быть решен путем итеративного подхода:

  • Получить ширину JTextArea
  • Используйте FontMetrics, чтобы получить ширину строки, как предположил jjnguy
  • разделите строку на слова.
  • Начните измерять witdh строки, добавляя по одному слову, пока не достигнете ширины области. Сохраните этот номер.
  • Как только вы достигнете этого, запустите новую итерацию, добавив по одному слову за раз (начиная с сохраненного номера).
  • Число строк будет числом итераций.

К сожалению, длина строки будет зависеть от шрифта и конкретной строки (а не для каждого символа равной ширине). Вы можете подсчитать количество символов в словах на каждой итерации и вернуть массив длин или среднюю длину.

Это связанный андроид вопрос: Как найти android TextView количество символов в строке?

Ответ 7

Я обнаружил, что подсчет количества строк с помощью текстового анализа только ничего не приводил. Вместо этого мое решение вычисляло его из предпочтительного размера текстовой области...

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();
            }
        });
    }
}