Writing at the bottom of JTextArea

2019-05-26 01:55发布

问题:

I have a JScrollPane and a JTextArea (editable) with 10 rows. I want the first user input to appear at the bottom of the JTextArea and the most recent input should push the previous input upward. To achieve this I use textArea.setMargin(new Insets(x,0,0,0)); and everything works as it should - except that my second input will toggle the JScrollPane.

How can I start at the bottom of the JTextArea and only enable scrolling when the entire original viewport is filled?

回答1:

Basically, you could add the JTextText onto a JPanel with another JPanel which acts a filler, causing the JTextArea to occupy the smallest space it actually needs.

I did this by using GridBagLayout to force the fill to occupy the most of space it could...

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestTextArea {

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

    public TestTextArea() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new JScrollPane(new TestPane()));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private JTextArea ta;

        public TestPane() {
            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            gbc.weightx = 1;
            gbc.weighty = 1;
            gbc.fill = GridBagConstraints.BOTH;

            JPanel filler = new JPanel();
            filler.setBackground(Color.WHITE);
            add(filler, gbc);

            gbc.gridy = 1;
            gbc.weighty = 0;
            ta = new JTextArea(1, 20);
            add(ta, gbc);
        }
    }

}


回答2:

To achieve this I use textArea.setMargin(new Insets(x,0,0,0)); and everything works as it should - except that my second input will toggle the JScrollPane.

Well I would guess that you would need to reset the margin every time you add text to the text area to take into account the new preferred size of the text area in relation to the size of the scroll pane.

You should be able to add a DocumentListener to the text area and make your adjustment whenever text is added to the Document.



回答3:

I don't think that using margins and insets is the way to go because you are using layout adjustments to achieve text (content) functionality. This should be controlled by the Document object, which is what JTextArea makes its calls to regarding its content.

If you are calling append internally, then override it in a new class extending JTextArea:

public class Test {

static MyTextArea ta = new MyTextArea();
static int x = 0;

    public static void main(String[] args) {

        ta.setRows(10);
        ta.setText("\n\n\n\n\n\n\n\n\n");
        ta.setCaretPosition(ta.getDocument().getLength());

        JButton append = new JButton("Append");
        append.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ta.append("\n" + x);
                x++;
            }
        });

        JFrame frame= new JFrame();
        frame.setContentPane(new JPanel(new BorderLayout()));
        frame.getContentPane().add(new JScrollPane(ta), BorderLayout.CENTER);
        frame.getContentPane().add(append, BorderLayout.LINE_START);

        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    static class MyTextArea extends JTextArea {

        @Override
        public void append(String str) {

            super.append(str);
            try {
                if (getDocument().getText(0, 1).equals("\n"))
                    getDocument().remove(0, 1);
            } catch (BadLocationException e) {
                e.printStackTrace();
            }
        }
    }
}

If you are editing it manually, add a DocumentListener:

public class Test {

    public static void main(String[] args) {

        JTextArea ta = new JTextArea();
        ta.setRows(10);
        ta.setText("\n\n\n\n\n\n\n\n\n");
        ta.setCaretPosition(ta.getDocument().getLength());
        ta.getDocument().addDocumentListener(new MyDocListener());

        JFrame frame= new JFrame();
        frame.setContentPane(new JPanel(new BorderLayout()));
        frame.getContentPane().add(new JScrollPane(ta), BorderLayout.CENTER);

        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    static class MyDocListener implements DocumentListener {

        @Override
        public void insertUpdate(DocumentEvent e) {

            final DocumentEvent de = e;

            Runnable pushUp = new Runnable() {

                @Override
                public void run() {

                    String t = null;
                    try {
                        t = de.getDocument().getText(de.getOffset(), de.getLength());
                        if (t.equals("\n") && de.getDocument().getText(0, 1).equals("\n"))
                            ta.getDocument().remove(0, 1);
                    } catch (BadLocationException e1) {
                        e1.printStackTrace();
                    }
                }
            };       
            SwingUtilities.invokeLater(pushUp);     
        }

        @Override
        public void removeUpdate(DocumentEvent e) {}

        @Override
        public void changedUpdate(DocumentEvent e) {}
    }
}

Note that all you need from the code are the inner classes, the rest is just so you can see it working. I also don't have information regarding the initial state of the text area. Here I just set the number of rows to 10 and the text to 10 empty lines. I'm also not sure what you're allowed to do in the text area. This solution assumes that you can't jump lines and each time you insert a line it's not blank and it follows the previous line.