I have a JScrollPane with a JTextArea set as its view port.
I update the (multi line) text shown on the JTextArea continously about once a second. Each time the text updates, JScrollPane goes all the way to the bottom of the text.
Instead, I'd like to figure out the line number that is currently shown as the first line in the original text, and have that line be the first line shown when the text has been updated (or if the new text doesn't have that many lines, then scroll all the way to the bottom).
My first attempt of doing this was to get the current caret position, figure the line based on that, and then set the text area to show that line:
int currentPos = textArea.getCaretPosition();
int currentLine = 0;
try {
for(int i = 0; i < textArea.getLineCount(); i++) {
if((currentPos >= textArea.getLineStartOffset(i)) && (currentPos < gameStateTextArea.getLineEndOffset(i))) {
currentLine = i;
break;
}
}
} catch(Exception e) { }
textArea.setText(text);
int newLine = Math.min(currentLine, textArea.getLineCount());
int newOffset = 0;
try {
newOffset = textArea.getLineStartOffset(newLine);
} catch(Exception e) { }
textArea.setCaretPosition(newOffset);
This was almost acceptable for my needs, but requires the user to click inside the text area to change the caret position, so that the scrolling will maintain state (which isn't nice).
How would I do this using the (vertical) scroll position instead ?
You can use JTextArea#setLineWrap(true) by default is set to false. Sets the line-wrapping policy of the text area. If set to true the lines will be wrapped if they are too long to fit within the allocated width. If set to false, the lines will always be unwrapped.
A Scrollbar is a Component, but not a Container. A ScrollPane is a Container. A ScrollPane handles its own events and performs its own scrolling.
The JTextArea class provides a component that displays multiple lines of text and optionally allows the user to edit the text.
A JScrollPane provides a scrollable view of a component. When screen real estate is limited, use a scroll pane to display a component that is large or one whose size can change dynamically. Other containers used to save screen space include split panes and tabbed panes.
I encountered the same problem and found that this answer includes a nice solution that works in this case:
DefaultCaret caret = (DefaultCaret) jTextArea.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
This is pieced together, untested, from the API documentation:
getViewport()
on your JScrollPane
to get a hold of the viewport.Viewport.getViewPosition()
to get the top-left coordinates. These are absolute, not a percentage of scrolled text.Viewport.addChangeListener()
to be notified when the top-left position changes (among other things). You may want to create a mechanism to distinguish user changes from changes your program makes, of course.Viewport.setViewPosition()
to set the top-left position to where it was before the disturbance.Update:
getScrollableTracksViewport{Height|Width}()
methods to return false
.Update 2:
The following code does what you want. It's amazing how much trouble I had to go to to get it to work:
setViewPosition
has to be postponed using invokeLater
because if it's done too early the text update will come after it and nullify its effect.Runnable
class in its constructor. I had been using the "global" instance of orig
and that kept setting my position to 0,0.public class Sami extends JFrame implements ActionListener {
public static void main(String[] args) {
(new Sami()).setVisible(true);
}
private JTextArea textArea;
private JScrollPane scrollPane;
private JButton moreTextButton = new JButton("More text!");
private StringBuffer text = new StringBuffer("0 Silly random text.\n");
private Point orig = new Point(0, 0);
public Sami() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
getContentPane().setLayout(new BorderLayout());
this.textArea = new JTextArea() {
@Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
@Override
public boolean getScrollableTracksViewportWidth() {
return false;
}
};
this.scrollPane = new JScrollPane(this.textArea);
getContentPane().add(this.scrollPane, BorderLayout.CENTER);
this.moreTextButton.addActionListener(this);
getContentPane().add(this.moreTextButton, BorderLayout.SOUTH);
setSize(400, 300);
}
@Override
public void actionPerformed(ActionEvent arg0) {
int lineCount = this.text.toString().split("[\\r\\n]").length;
this.text.append(lineCount + "The quick brown fox jumped over the lazy dog.\n");
Point orig = this.scrollPane.getViewport().getViewPosition();
// System.out.println("Orig: " + orig);
this.textArea.setText(text.toString());
SwingUtilities.invokeLater(new LaterUpdater(orig));
}
class LaterUpdater implements Runnable {
private Point o;
public LaterUpdater(Point o) {
this.o = o;
}
public void run() {
// System.out.println("Set to: " + o);
Sami.this.scrollPane.getViewport().setViewPosition(o);
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With