I'd like to know if there is a way to know when a JScrollBar (vertical in my case) has reached the bottom of his containing JScrollPane.
At first i have though of using an AdjustmentListener on the scroll bar but i don't know how to interpret the value attribute of the JScrollBar. Also i'm not sure to properly understand what the maximum represents and if i can use with the value to get the information i need.
Edit:
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent ae) {
System.out.println("Value: " + scrollPane.getVerticalScrollBar().getValue() + " Max: " + scrollPane.getVerticalScrollBar().getMaximum());
}
}
You have to add the extent of the scrollbar to your calculation. I added the code into your code in the example below.
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent ae) {
int extent = scrollPane.getVerticalScrollBar().getModel().getExtent();
System.out.println("Value: " + (scrollPane.getVerticalScrollBar().getValue()+extent) + " Max: " + scrollPane.getVerticalScrollBar().getMaximum());
}
});
Two alternative implementations (partially reacting to Kleopatra)
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent event) {
JScrollBar scrollBar = (JScrollBar) event.getAdjustable();
int extent = scrollBar.getModel().getExtent();
System.out.println("1. Value: " + (scrollBar.getValue() + extent) + " Max: " + scrollBar.getMaximum());
}
});
Or via the model
scrollPane.getVerticalScrollBar().getModel().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent event) {
BoundedRangeModel model = (BoundedRangeModel) event.getSource();
int extent = model.getExtent();
int maximum = model.getMaximum();
int value = model.getValue();
System.out.println("2. Value: " + (value + extent) + " Max: " + maximum);
}
});
#StanislavL almost had the correct answer.
Try scrollBar.getValue() == scrollBar.getMaximum() - scrollBar.getVisibleAmount().
To understand why the getVisibleAmount() call is necessary, you must first understand how JScrollBars work. A JScrollBar has 4 values: minimum, maximum, value, and extent. value is the top of the scroll handle itself, and extent is the effective length of the scroll handle. value will never equal maximum unless the scroll handle has a length of 0. To compensate, we must adjust the value we are comparing against by subtracting the length of the scroll handle to get the effective maximum.
This effect is documented here, which misleadingly makes it sound like getMaximum() adjusts the value like it should, but looking closely at the implementation of JScrollBar shows that getMaximum() actually gives us the true maximum, not the effective maximum.
By using the viewport of the JScrollPane you can calculate if the viewport is viewing the end of the component.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ViewPortDemo extends JPanel{
public ViewPortDemo(){
super();
JTree tree = new JTree();
for(int i =0; i < tree.getRowCount(); i++){
tree.expandRow(i);
}
final JScrollPane pane = new JScrollPane(tree){
Dimension prefSize = new Dimension(200,150);
public Dimension getPreferredSize(){
return prefSize;
}
};
pane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
JViewport vp = pane.getViewport();
if(vp.getView().getHeight() <= vp.getHeight() + vp.getViewPosition().y){
System.out.println("End");
}
}
});
add(pane);
}
public static void main(String[] args){
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new ViewPortDemo());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
scrollBar.getValue()==scrollBar.getMaximum()
Related
I'm writing a thing that has a JLabel array that has something in the first index and then detects right keys that shifts the thing to the right. Basically, [x][][][][][][] starts, then if I press the right key, it shifts to [][x][][][][][]. Currently, I can't get the JLabel to properly update. It shows up correctly at first, but when I hit the right key, the entire thing gets messed up. Ie, it starts like this:
(I have the numbers to gauge the shifting, they'll go away when I figure this problem out)
Which is right, but when I hit the right key, everything disappears (just blank.) However, my test in the console still gives me the correct values though.
The numbers don't even show up :/
Here's the relevant code:
public Template() {
for (int i = 0; i < 7; i++) {
positions[i] = new JLabel();
positions[i].setFont(new Font("Times New Roman", Font.PLAIN, 15));
System.out.println("Yay");
}
// I initialize my JLabel array here instead of below because if I don't do this, there's no change whatsoever if I press the right key. I'm not sure if I did it correctly, though.
initialize(p1, 0);
}
private void initialize(String p, int index) {
frame = new JFrame();
JPanel header = new JPanel();
header.setBackground(Color.WHITE);
GridBagLayout gbl = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
header.setLayout(gbl);
header.setPreferredSize(new Dimension(400, 50));
c.fill = GridBagConstraints.HORIZONTAL;
for (int i = 0; i < 7; i++) {
if (i == index) {
positions[i].setText(p + "");
System.out.println("indexed set");
} else {
positions[i].setText("" + i);
System.out.println(positions[i].getText());
System.out.println("set");
}
c.gridx = i;
c.gridy = 0;
header.add(positions[i], c);
}
frame.add(header, BorderLayout.NORTH);
frame.setBounds(100, 100, 450, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
playMove(p);
}
public void playMove(String p) {
...
frame.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent keyEvent) {
System.out.println("Game.keyPressed -- event: " + keyEvent);
if (keyEvent.getKeyCode() == KeyEvent.VK_RIGHT) {
System.out.println("Right key pressed!");
int index = searchHeader();
System.out.println("index: " + index);
if (index != 6) {
int shift = index + 1;
System.out.println("shift: " + shift);
SwingUtilities.updateComponentTreeUI(frame);
initialize(p, shift, t);
}
}
}
#Override
public void keyReleased(KeyEvent keyEvent) {
System.out.println("Game.keyReleased -- event: " + keyEvent);
}
});
}
public int searchHeader() {
for (int i = 0; i < 7; i++) {
String val = positions[i].getText();
if (val == "x" || val == "o") {
System.out.println("i:" + i);
return i;
}
}
return 0;
}
My System.out.println(); tests all return the correct values (the correct index, shift, and numbers in the array) but I'm not sure why the thing isn't showing up :/
If my code needs clarification or missed something (I did cut some irrelevant info off after taking advice from my last post) please tell me
Since you didn't provide a minimal runnable example that we could copy into our IDE, run, and debug, I came up with this example GUI that shows an X shifting to the right.
I used the right arrow key to perform the shift.
Here's the GUI when starting the application.
Here's the GUI after shifting the X three positions.
Here's the GUI after shifting the X four positions.
Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Netbeans section.
The first thing I did was start the Swing application by calling the SwingUtilities invokeLater method. This method ensures that the Swing components are created and executed on the Event Dispatch Thread.
Next, I created a JFrame. The JFrame methods must be called in a specific order. This is the order I use for my Swing applications.
I created a main JPanel to hold the array of eight JLabels. I also created an int to hold the position of the X. I used a FlowLayout to lay out the array of JLabels. I( used the Box class to put some space between the JLabels.
As you're creating a more complicated game, create an application model using plain Java getter/setter classes.
I used Key Bindings to bind the right arrow key to the Action class. I used AbstractAction so I'd only have to code the actionPerformed method.
Here's the complete runnable code.
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
public class ShiftRightExample implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new ShiftRightExample());
}
private int position;
private JLabel[] spotLabel;
public ShiftRightExample() {
this.position = 0;
}
#Override
public void run() {
JFrame frame = new JFrame("Shift Right Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel mainPanel = createMainPanel();
frame.add(mainPanel, BorderLayout.CENTER);
setKeyBindings(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createMainPanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
this.spotLabel = new JLabel[8];
for (int index = 0; index < spotLabel.length; index++) {
spotLabel[index] = new JLabel(" ");
panel.add(spotLabel[index]);
if (index < (spotLabel.length - 1)) {
panel.add(Box.createHorizontalStrut(20));
}
}
updateMainPanel(position, "X");
return panel;
}
private void setKeyBindings(JPanel panel) {
panel.getInputMap().put(
KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "shiftRight");
panel.getActionMap().put("shiftRight", new ShiftRightAction());
}
public void updateMainPanel(int position, String value) {
spotLabel[position].setText(value);
}
public class ShiftRightAction extends AbstractAction {
private static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent event) {
updateMainPanel(position, " ");
position++;
position = position % spotLabel.length;
updateMainPanel(position, "X");
}
}
}
I'd like to know if there is a way to know when a JScrollBar (vertical in my case) has reached the bottom of his containing JScrollPane.
At first i have though of using an AdjustmentListener on the scroll bar but i don't know how to interpret the value attribute of the JScrollBar. Also i'm not sure to properly understand what the maximum represents and if i can use with the value to get the information i need.
Edit:
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent ae) {
System.out.println("Value: " + scrollPane.getVerticalScrollBar().getValue() + " Max: " + scrollPane.getVerticalScrollBar().getMaximum());
}
}
You have to add the extent of the scrollbar to your calculation. I added the code into your code in the example below.
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent ae) {
int extent = scrollPane.getVerticalScrollBar().getModel().getExtent();
System.out.println("Value: " + (scrollPane.getVerticalScrollBar().getValue()+extent) + " Max: " + scrollPane.getVerticalScrollBar().getMaximum());
}
});
Two alternative implementations (partially reacting to Kleopatra)
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent event) {
JScrollBar scrollBar = (JScrollBar) event.getAdjustable();
int extent = scrollBar.getModel().getExtent();
System.out.println("1. Value: " + (scrollBar.getValue() + extent) + " Max: " + scrollBar.getMaximum());
}
});
Or via the model
scrollPane.getVerticalScrollBar().getModel().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent event) {
BoundedRangeModel model = (BoundedRangeModel) event.getSource();
int extent = model.getExtent();
int maximum = model.getMaximum();
int value = model.getValue();
System.out.println("2. Value: " + (value + extent) + " Max: " + maximum);
}
});
#StanislavL almost had the correct answer.
Try scrollBar.getValue() == scrollBar.getMaximum() - scrollBar.getVisibleAmount().
To understand why the getVisibleAmount() call is necessary, you must first understand how JScrollBars work. A JScrollBar has 4 values: minimum, maximum, value, and extent. value is the top of the scroll handle itself, and extent is the effective length of the scroll handle. value will never equal maximum unless the scroll handle has a length of 0. To compensate, we must adjust the value we are comparing against by subtracting the length of the scroll handle to get the effective maximum.
This effect is documented here, which misleadingly makes it sound like getMaximum() adjusts the value like it should, but looking closely at the implementation of JScrollBar shows that getMaximum() actually gives us the true maximum, not the effective maximum.
By using the viewport of the JScrollPane you can calculate if the viewport is viewing the end of the component.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ViewPortDemo extends JPanel{
public ViewPortDemo(){
super();
JTree tree = new JTree();
for(int i =0; i < tree.getRowCount(); i++){
tree.expandRow(i);
}
final JScrollPane pane = new JScrollPane(tree){
Dimension prefSize = new Dimension(200,150);
public Dimension getPreferredSize(){
return prefSize;
}
};
pane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
JViewport vp = pane.getViewport();
if(vp.getView().getHeight() <= vp.getHeight() + vp.getViewPosition().y){
System.out.println("End");
}
}
});
add(pane);
}
public static void main(String[] args){
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new ViewPortDemo());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
scrollBar.getValue()==scrollBar.getMaximum()
I'm working on a text editor, the main panel is composed of 3 JTextPanes, 2 on the side which show the number of line and common syntax errors, and 1 in the middle for the main edition. The whole stuff is packed in a JScrollPane.
The autoscroll issue appears when the users jump a line (press ENTER), the KeyListeners attached add a new entry in the 2 sides JTextPanes (num and syntax error for the line),
in reaction, the JScrollPane autoscroll in the bottom of the docs, probably to show the new text inserted in the 2 sides JTextPanes.
I partially fix the problem by setting the JScrollBar's position for each new line (added by the user) in my KeyListeners. Using scrollRectToVisible for example, or better by selecting a proper part of text in one of the 2 sides JTextPanes.
However, the final effect is not so great, for each new line the vertical scrollbar oscillates, and we can easily crash the app by pressing ENTER for a few seconds. I've been looking for solutions with a lot of methods of the JScrollPane class and trying AdjustmentListener but unsuccessfully. Would you help me?
PS: Sorry for my English. I am French, our forums suck.
SSCCE great source of inspiration, as this one worked well (couldn't see my problem when running it) it seems that my method actually works, but was not running in the right listener in my real code.
Thanks anyway!
There is the SSCCE, its a simple JScrollPane composed of one central JTextPane for edition and one lateral for the lines number. The placeScroll() method place the scrollbar so the caret in the main JTextPane is in the middle (vertically) when the paneLigne try to push it down.
Bye
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.awt.BorderLayout;
import javax.swing.JScrollPane;
public class SSCCE extends JFrame {
private JTextPane paneLigne, main;
private String tempchain;
public SSCCE() {
this.setSize(500,500);
this.setTitle("S");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
paneLigne = new JTextPane();
paneLigne.setEditable(false);
tempchain = "";
for(int j=1; j<40; j++)
tempchain+=" " + j + " \n";
paneLigne.setText(tempchain);
main = new JTextPane();
main.addKeyListener(new KeyListener() {
private int lastnline=0;
public void keyPressed(KeyEvent arg0) {
if(arg0.getKeyCode()==10) {
String tecste=main.getText();
int n=nbLignes(tecste);
if(n>38) {
if(lastnline<n) {
paneLigne.setText(paneLigne.getText()+" " + (n+1) + " \n");
} else {
this.retablirLignes(tecste);
}
} else {
paneLigne.setText(tempchain);
}
lastnline=n;
this.placeScroll();
}
}
#Override
public void keyReleased(KeyEvent arg0) { }
#Override
public void keyTyped(KeyEvent arg0) { }
private int nbLignes(String str) {
int ret=1;
for(int n=0, cpt=0; (n=str.indexOf('\n', cpt))!=-1; cpt=n+1)
ret++;
return ret;
}
public void retablirLignes(String stret) {
int n=this.nbLignes(stret);
String retoure="";
if(n>=40) {
for(int j=1; j<n+2; j++)
retoure+=" " + j + " \n";
paneLigne.setText(retoure);
}
lastnline=n;
}
public void placeScroll() {
// TODO Auto-generated method stub
if(paneLigne!=null) {
int n=this.nbLignesBuen(main.getText().substring(0, main.getCaretPosition()));
if(n!=-1) {
paneLigne.select(paneLigne.getText().indexOf(""+n), n+1);
} else {
paneLigne.select(0,1);
}
}
}
private int nbLignesBuen(String str) { //return the index of the last 20th line
int ret=0;
for(int n, cpt=0; (n=str.indexOf('\n', cpt))!=-1; cpt=n+1)
ret++;
if(ret>20)
ret-=20;
else
ret=-1;
return ret;
}
});
JPanel contentpane=new JPanel(new BorderLayout());
contentpane.add(paneLigne, BorderLayout.WEST);
contentpane.add(main, BorderLayout.CENTER);
this.setContentPane(new JScrollPane(contentpane));
this.setVisible(true);
}
public static void main(String[] args) {
SSCCE fen = new SSCCE();
}
}
How to make JTextPane autoscroll only when scroll bar is at bottom and scroll lock is off? This shouldn't have anything to do with caret, which is what I seem to be finding all over Google. :(
I think my program below meets your requirements exactly, with one possible caveat: you're not allowed to type in the text area. So this would be good for a log viewer, but not an interactive console. The code runs a little long because I have made it into a ready-to-run demo of the approach. I suggest running the program as-is and checking out the behavior. If the behavior works well for you, then invest a little time in studying the code. I have included comments in the code to highlight some of the more important sections.
Update 2013-07-17: You may also want to check out random dude's solution in his separate answer farther down the page. His approach is more elegant than mine.
Also see Swing: Scroll to bottom of JScrollPane, conditional on current viewport location for a potential solution that does not interfere with the caret position.
SCCE source code follows:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.Timer;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
public class ScrollingJTextAreaExample extends JFrame {
// Worker thread to help periodically append example messages to JTextArea
Timer timer = new Timer();
// Merely informative counter, will be displayed with the example messages
int messageCounter = 0;
// GUI components
JScrollPane jScrollPane;
JTextArea jTextArea;
public ScrollingJTextAreaExample() {
initComponents(); // Boiler plate GUI construction and layout
// Configure JTextArea to not update the cursor position after
// inserting or appending text to the JTextArea. This disables the
// JTextArea's usual behavior of scrolling automatically whenever
// inserting or appending text into the JTextArea: we want scrolling
// to only occur at our discretion, not blindly. NOTE that this
// breaks normal typing into the JTextArea. This approach assumes
// that all updates to the ScrollingJTextArea are programmatic.
DefaultCaret caret = (DefaultCaret) jTextArea.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
// Schedule a task to periodically append example messages to jTextArea
timer.schedule(new TextGeneratorTask(), 250, 250);
// This DocumentListener takes care of re-scrolling when appropriate
Document document = jTextArea.getDocument();
document.addDocumentListener(new ScrollingDocumentListener());
}
// Boring, vanilla GUI construction and layout code
private void initComponents() {
jScrollPane = new javax.swing.JScrollPane();
jTextArea = new javax.swing.JTextArea();
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
jScrollPane.setViewportView(jTextArea);
getContentPane().add(jScrollPane, java.awt.BorderLayout.CENTER);
setSize(320, 240);
setLocationRelativeTo(null);
}
// ScrollingDocumentListener takes care of re-scrolling when appropriate
class ScrollingDocumentListener implements DocumentListener {
public void changedUpdate(DocumentEvent e) {
maybeScrollToBottom();
}
public void insertUpdate(DocumentEvent e) {
maybeScrollToBottom();
}
public void removeUpdate(DocumentEvent e) {
maybeScrollToBottom();
}
private void maybeScrollToBottom() {
JScrollBar scrollBar = jScrollPane.getVerticalScrollBar();
boolean scrollBarAtBottom = isScrollBarFullyExtended(scrollBar);
boolean scrollLock = Toolkit.getDefaultToolkit()
.getLockingKeyState(KeyEvent.VK_SCROLL_LOCK);
if (scrollBarAtBottom && !scrollLock) {
// Push the call to "scrollToBottom" back TWO PLACES on the
// AWT-EDT queue so that it runs *after* Swing has had an
// opportunity to "react" to the appending of new text:
// this ensures that we "scrollToBottom" only after a new
// bottom has been recalculated during the natural
// revalidation of the GUI that occurs after having
// appending new text to the JTextArea.
EventQueue.invokeLater(new Runnable() {
public void run() {
EventQueue.invokeLater(new Runnable() {
public void run() {
scrollToBottom(jTextArea);
}
});
}
});
}
}
}
class TextGeneratorTask extends TimerTask {
public void run() {
EventQueue.invokeLater(new Runnable() {
public void run() {
String message = (++messageCounter)
+ " Lorem ipsum dolor sit amet, consectetur"
+ " adipisicing elit, sed do eiusmod tempor"
+ " incididunt ut labore et dolore magna aliqua.\n";
jTextArea.append(message);
}
});
}
}
public static boolean isScrollBarFullyExtended(JScrollBar vScrollBar) {
BoundedRangeModel model = vScrollBar.getModel();
return (model.getExtent() + model.getValue()) == model.getMaximum();
}
public static void scrollToBottom(JComponent component) {
Rectangle visibleRect = component.getVisibleRect();
visibleRect.y = component.getHeight() - visibleRect.height;
component.scrollRectToVisible(visibleRect);
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new ScrollingJTextAreaExample().setVisible(true);
}
});
}
}
Little late to this question, but I came up with this solution.
conversationPane = new JTextPane();
final JScrollPane conversationScrollPane = new JScrollPane(conversationPane);
conversationScrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
BoundedRangeModel brm = conversationScrollPane.getVerticalScrollBar().getModel();
boolean wasAtBottom = true;
public void adjustmentValueChanged(AdjustmentEvent e) {
if (!brm.getValueIsAdjusting()) {
if (wasAtBottom)
brm.setValue(brm.getMaximum());
} else
wasAtBottom = ((brm.getValue() + brm.getExtent()) == brm.getMaximum());
}
});
Seems to work perfectly for my needs. Little explanation: Essentially if the scroll bar is not being moved by a person and the bar was last at the maximum/bottom then reset it to the maximum. If it's being manually adjusted, then check to see if it was adjusted to be at the bottom.
Text Area Scrolling may be of interest.
I have no idea how the scroll lock key affects it. I found the following from the Wikipedia page on Scroll Lock:
Therefore, Scroll Lock can be regarded as a defunct feature in almost all modern programs and operating systems.
So I wouldn't worry about it.
I needed to do the same for a logging text area. The solutions I found on the web did not worked for me (they either stop auto scrolling when logging to much messages quickly, or they blocked the scrollbar at bottom even if you scroll up whith your mouse wheel).
I did it this way :
public static void makeTextAreaAutoScroll(JTextArea textArea) {
// Get the text area's scroll pane :
final JScrollPane scrollPane = (JScrollPane) (textArea.getParent().getParent());
// Disable the auto scroll :
((DefaultCaret)textArea.getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
// Add a listener to the vertical scroll bar :
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
private int _val = 0;
private int _ext = 0;
private int _max = 0;
private final BoundedRangeModel _model = scrollPane.getVerticalScrollBar().getModel();
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
// Get the new max :
int newMax = _model.getMaximum();
// If the new max has changed and if we were scrolled to bottom :
if (newMax != _max && (_val + _ext == _max) ) {
// Scroll to bottom :
_model.setValue(_model.getMaximum() - _model.getExtent());
}
// Save the new values :
_val = _model.getValue();
_ext = _model.getExtent();
_max = _model.getMaximum();
}
});
}
Just use it this way :
makeTextAreaAutoScroll(yourTextArea);
You can test with this piece of code :
new Timer().schedule(new TimerTask() {
#Override
public void run() {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
String line = "test " + Math.random();
yourTextArea.append(yourTextArea.getText().isEmpty() ? line : "\n" + line);
}
});
}
}, 0, 5);
Now your text area should auto scroll if the scroll bar is at bottom, stop auto scrolling if you move the scroll bar (by dragging the bar or by using the wheel), and auto scroll again if you put the scroll bar at bottom again.
Try this :
JTextArea txt = new JTextArea();
JScrollPane jsp = new JScrollPane(history, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
txt.setCaretPosition(txt.getDocument().getLength()); // do this afeter any event
Hope that helps you
After I read Mike Clark and random dude's solution, I end up with below snippet code.
private boolean doAutoScroll = true;
private JTextPane textPane;
private JScrollPane scrollPane;
public void setup() {
/* Left Panel */
textPane = new JTextPane();
textPane.setPreferredSize(new Dimension(600, 400)); // width, height
/*
* Not update the cursor position after inserting or appending text to the JTextPane.
* [NOTE]
* This breaks normal typing into the JTextPane.
* This approach assumes that all updates to the JTextPane are programmatic.
*/
DefaultCaret caret = (DefaultCaret) textPane.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
scrollPane = new JScrollPane(textPane);
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
BoundedRangeModel brm = scrollPane.getVerticalScrollBar().getModel();
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
// Invoked when user select and move the cursor of scroll by mouse explicitly.
if (!brm.getValueIsAdjusting()) {
if (doAutoScroll) brm.setValue(brm. getMaximum());
} else {
// doAutoScroll will be set to true when user reaches at the bottom of document.
doAutoScroll = ((brm.getValue() + brm.getExtent()) == brm.getMaximum());
}
}
});
scrollPane.addMouseWheelListener(new MouseWheelListener() {
BoundedRangeModel brm = scrollPane.getVerticalScrollBar().getModel();
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
// Invoked when user use mouse wheel to scroll
if (e.getWheelRotation() < 0) {
// If user trying to scroll up, doAutoScroll should be false.
doAutoScroll = false;
} else {
// doAutoScroll will be set to true when user reaches at the bottom of document.
doAutoScroll = ((brm.getValue() + brm.getExtent()) == brm.getMaximum());
}
}
});
}
The difference is that it is additionally using MouseWheelListener to update doAutoScroll flag even if user uses mouse wheel to scroll up and down.
I am attempting to implmement smart autoscrolling on a JScrollPane containing a JTextPane. The JTextPane is used for logging my app in color. However I'm running into a wall trying to do smart autoscrolling. By smart autoscrolling I don't mean blindly autoscrolling every time something changes, I mean checking to see if your scrolled all the way down, then autoscroll. However no matter what I do it either always autoscrolls or doesn't at all
As a test script, here's the setup (the JFrame has been left out)
final JTextPane textPane = new JTextPane();
textPane.setEditable(false);
final JScrollPane contentPane = new JScrollPane(textPane);
contentPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
And here's the ugly auto add test loop
while (true)
try {
Thread.sleep(1000);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
JScrollBar scrollBar = scroll;
boolean preCheck = ((scrollBar.getVisibleAmount() != scrollBar.getMaximum()) && (scrollBar.getValue() + scrollBar.getVisibleAmount() == scrollBar.getMaximum()));
System.out.println("Value: " + scroll.getValue()
+ " | Visible: " + scrollBar.getVisibleAmount()
+ " | Maximum: " + scrollBar.getMaximum()
+ " | Combined: " + (scrollBar.getValue() + scrollBar.getVisibleAmount())
+ " | Vis!=Max : " + (scrollBar.getVisibleAmount() != scrollBar.getMaximum())
+ " | Comb=Max: " + (scrollBar.getValue() + scrollBar.getVisibleAmount() == scrollBar.getMaximum())
+ " | Eval: " + preCheck);
StyledDocument doc = textPane.getStyledDocument();
doc.insertString(doc.getLength(), "FAGAHSIDFNJASDKFJSD\n", doc.getStyle(""));
if (!preCheck)
textPane.setCaretPosition(doc.getLength());
} catch (BadLocationException ex) {
ex.printStackTrace();
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
Its not pretty, but it gets the job done.
Here's though the relevant check
boolean preCheck = ((scrollBar.getVisibleAmount() != scrollBar.getMaximum()) && (scrollBar.getValue() + scrollBar.getVisibleAmount() == scrollBar.getMaximum()));
if (preCheck)
textPane.setCaretPosition(doc.getLength());
Thats the part thats been giving me trouble. There is first a check to see if the bar is visible but unusable (not enough text, making the bar the full length), then if the bottom of the bar is equal to the maximum. In theory, that should work. However nothing, including moving the check around, has gotten the results I would like.
Any suggestions?
NOT A DUPLICATE of this or this, as they are wanting it to always scroll, not just sometimes.
Edit:
I replaced the following code with a more flexible version that will work on any component in a JScrollPane. Check out: Smart Scrolling.
import java.awt.*;
import java.awt.event.*;
import java.util.Date;
import javax.swing.*;
import javax.swing.text.*;
public class ScrollControl implements AdjustmentListener
{
private JScrollBar scrollBar;
private JTextComponent textComponent;
private int previousExtent = -1;
public ScrollControl(JScrollPane scrollPane)
{
Component view = scrollPane.getViewport().getView();
if (! (view instanceof JTextComponent))
throw new IllegalArgumentException("Scrollpane must contain a JTextComponent");
textComponent = (JTextComponent)view;
scrollBar = scrollPane.getVerticalScrollBar();
scrollBar.addAdjustmentListener( this );
}
#Override
public void adjustmentValueChanged(final AdjustmentEvent e)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
checkScrollBar(e);
}
});
}
private void checkScrollBar(AdjustmentEvent e)
{
// The scroll bar model contains information needed to determine the
// caret update policy.
JScrollBar scrollBar = (JScrollBar)e.getSource();
BoundedRangeModel model = scrollBar.getModel();
int value = model.getValue();
int extent = model.getExtent();
int maximum = model.getMaximum();
DefaultCaret caret = (DefaultCaret)textComponent.getCaret();
// When the size of the viewport changes there is no need to change the
// caret update policy.
if (previousExtent != extent)
{
// When the height of a scrollpane is decreased the scrollbar is
// moved up from the bottom for some reason. Reposition the
// scrollbar at the bottom
if (extent < previousExtent
&& caret.getUpdatePolicy() == DefaultCaret.UPDATE_WHEN_ON_EDT)
{
scrollBar.setValue( maximum );
}
previousExtent = extent;
return;
}
// Text components will not scroll to the bottom of a scroll pane when
// a bottom inset is used. Therefore the location of the scrollbar,
// the height of the viewport, and the bottom inset value must be
// considered when determining if the scrollbar is at the bottom.
int bottom = textComponent.getInsets().bottom;
if (value + extent + bottom < maximum)
{
if (caret.getUpdatePolicy() != DefaultCaret.NEVER_UPDATE)
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
}
else
{
if (caret.getUpdatePolicy() != DefaultCaret.UPDATE_WHEN_ON_EDT)
{
caret.setDot(textComponent.getDocument().getLength());
caret.setUpdatePolicy(DefaultCaret.UPDATE_WHEN_ON_EDT);
}
}
}
private static void createAndShowUI()
{
JPanel center = new JPanel( new GridLayout(1, 2) );
String text = "1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n";
final JTextArea textArea = new JTextArea();
textArea.setText( text );
textArea.setEditable( false );
center.add( createScrollPane( textArea ) );
System.out.println(textArea.getInsets());
final JTextPane textPane = new JTextPane();
textPane.setText( text );
textPane.setEditable( false );
center.add( createScrollPane( textPane ) );
textPane.setMargin( new Insets(5, 3, 7, 3) );
System.out.println(textPane.getInsets());
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(center, BorderLayout.CENTER);
frame.setSize(500, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Timer timer = new Timer(2000, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
try
{
Date now = new Date();
textArea.getDocument().insertString(textArea.getDocument().getLength(), "\n" + now.toString(), null);
textPane.getDocument().insertString(textPane.getDocument().getLength(), "\n" + now.toString(), null);
}
catch (BadLocationException e1) {}
}
});
timer.start();
}
private static JComponent createScrollPane(JComponent component)
{
JScrollPane scrollPane = new JScrollPane(component);
new ScrollControl( scrollPane );
return scrollPane;
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}