Как рассчитать количество строк (и столбцов в каждой строке) текст занимает в JTextArea? - PullRequest
12 голосов
/ 12 мая 2011

После того, как заинтересовался проблемой, представленной в вопросе Я пытался подойти к нему несколько раз и потерпел неудачу, и мне это не нравится :)

Я думаю, что если бы проблема была разбита на подзадачи, это могло бы помочь решить ее.

Для простоты предположим, что 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, «<» был изменен на «<=») в setText области. </em>

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

РЕДАКТИРОВАТЬ3: Это решение, которое я быстро взломал, по крайней мере, теперь показанные символы и расчетные точно такие же. Может кто-то еще, пожалуйста, проверьте это и дайте мне знать, возможно, как это работает на другой машине, чем 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 ]

10 голосов
/ 14 мая 2011

Что я делаю не так, о чем я забываю?

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

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

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

  3. TextLayout показывает еще более узкие границы, но обратите внимание на «базовые относительные координаты».

  4. Метод 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());
    }
}
5 голосов
/ 12 мая 2011

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

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();
    }
}
5 голосов
/ 12 мая 2011

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

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 голосов
/ 12 мая 2011

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

2 голосов
/ 22 сентября 2011

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

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();
            }
        });
    }
}
2 голосов
/ 12 мая 2011

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

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

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

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

2 голосов
/ 12 мая 2011

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

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

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