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