Smart JScrollPane autoscrolling - java

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();
}
});
}
}

Related

How to change default style of Java Swing application in NetBeans?

There are few styles in the preview mode but can't find where I can set the preferred design. Is there any option?
Check screen
Examples:
Programmatically:
UIManager.setLookAndFeel("com.sun.java.swing.plaf.gtk.GTKLookAndFeel");
Command line:
java -Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel MyApp
You can find more info about this on the Oracle docs.
A complete demo (from the docs):
public class LookAndFeelDemo implements ActionListener {
private static String labelPrefix = "Number of button clicks: ";
private int numClicks = 0;
final JLabel label = new JLabel(labelPrefix + "0 ");
// Specify the look and feel to use by defining the LOOKANDFEEL constant
// Valid values are: null (use the default), "Metal", "System", "Motif",
// and "GTK"
final static String LOOKANDFEEL = "Metal";
// If you choose the Metal L&F, you can also choose a theme.
// Specify the theme to use by defining the THEME constant
// Valid values are: "DefaultMetal", "Ocean", and "Test"
final static String THEME = "Test";
public Component createComponents() {
JButton button = new JButton("I'm a Swing button!");
button.setMnemonic(KeyEvent.VK_I);
button.addActionListener(this);
label.setLabelFor(button);
JPanel pane = new JPanel(new GridLayout(0, 1));
pane.add(button);
pane.add(label);
pane.setBorder(BorderFactory.createEmptyBorder(
30, //top
30, //left
10, //bottom
30) //right
);
return pane;
}
public void actionPerformed(ActionEvent e) {
numClicks++;
label.setText(labelPrefix + numClicks);
}
private static void initLookAndFeel() {
String lookAndFeel = null;
if (LOOKANDFEEL != null) {
if (LOOKANDFEEL.equals("Metal")) {
lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName();
// an alternative way to set the Metal L&F is to replace the
// previous line with:
// lookAndFeel = "javax.swing.plaf.metal.MetalLookAndFeel";
}
else if (LOOKANDFEEL.equals("System")) {
lookAndFeel = UIManager.getSystemLookAndFeelClassName();
}
else if (LOOKANDFEEL.equals("Motif")) {
lookAndFeel = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
}
else if (LOOKANDFEEL.equals("GTK")) {
lookAndFeel = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
}
else {
System.err.println("Unexpected value of LOOKANDFEEL specified: "
+ LOOKANDFEEL);
lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName();
}
try {
UIManager.setLookAndFeel(lookAndFeel);
// If L&F = "Metal", set the theme
if (LOOKANDFEEL.equals("Metal")) {
if (THEME.equals("DefaultMetal"))
MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme());
else if (THEME.equals("Ocean"))
MetalLookAndFeel.setCurrentTheme(new OceanTheme());
else
MetalLookAndFeel.setCurrentTheme(new TestTheme());
UIManager.setLookAndFeel(new MetalLookAndFeel());
}
}
catch (ClassNotFoundException e) {
System.err.println("Couldn't find class for specified look and feel:"
+ lookAndFeel);
System.err.println("Did you include the library in the class path?");
System.err.println("Using the default look and feel.");
}
catch (UnsupportedLookAndFeelException e) {
System.err.println("Can't use the specified look and feel ("
+ lookAndFeel
+ ") on this platform.");
System.err.println("Using the default look and feel.");
}
catch (Exception e) {
System.err.println("Couldn't get specified look and feel ("
+ lookAndFeel
+ "), for some reason.");
System.err.println("Using the default look and feel.");
e.printStackTrace();
}
}
}
private static void createAndShowGUI() {
//Set the look and feel.
initLookAndFeel();
//Make sure we have nice window decorations.
JFrame.setDefaultLookAndFeelDecorated(true);
//Create and set up the window.
JFrame frame = new JFrame("SwingApplication");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
LookAndFeelDemo app = new LookAndFeelDemo();
Component contents = app.createComponents();
frame.getContentPane().add(contents, BorderLayout.CENTER);
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
//Schedule a job for the event dispatch thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}

An UI that slides through images

I am trying to change what image is shown in the JLabels of my User Interfaces.
The following class is a very simple test of the concept. The UI takes a folder full of images (the field imageFolderPath) and displays the first image, resized, in the only JLabel; clicking on the image prompts the UI to display the following image in the folder.
At least, it should. In reality, no image is shown. The fault is obviously of the method reloadImage(), either while rescaling the image or while repainting the JLabel, but I have not managed to find or correct the problem. Any idea?
public class Test extends JFrame {
private JPanel contentPane;
private JLabel boardImage;
private ImageIcon icon;
public String imageFolderPath = "C:\\modify\\it\\to\\suit\\your\\needs\\";
public File[] files;
public int indexImage = 0;
public int imageResolution = 450;
// =========================================================
// TODO | Constructors
/**
* Create the frame.
*/
public Test() {
if( !new File(imageFolderPath).exists() )
new File(imageFolderPath).mkdir();
this.files = new File(imageFolderPath).listFiles();
System.out.println("Can show:");
for( File file : files )
System.out.println("\t"+file.getName());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, imageResolution, imageResolution);
contentPane = new JPanel();
contentPane.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent arg0) {
// Every time the image is clicked, another one is shown
indexImage++;
reloadImage();
}
});
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
boardImage = new JLabel();
boardImage.setBounds(0, 0, imageResolution, imageResolution);
reloadImage();
contentPane.add(boardImage);
}
// =========================================================
// TODO | Support methods
/** Reloads the image of the {#link ChessBoard} at its current state. */
public void reloadImage() {
if( files[indexImage % files.length].exists() )
System.out.println("The file " + files[indexImage % files.length].getName() + " exists");
else System.out.println("The file " + files[indexImage % files.length].getName() + " doesnt exists");
// Reload the image - resized
BufferedImage buffer = null;
try {
buffer = ImageIO.read( files[indexImage % files.length] );
} catch (IOException e) { e.printStackTrace(); }
Image rescaledImage = buffer.getScaledInstance(imageResolution, imageResolution, Image.SCALE_SMOOTH);
icon = new ImageIcon(rescaledImage);
boardImage = new JLabel(icon);
// boardImage.setText( files[indexImage % files.length].getName() );
System.out.println("Is now showing " + files[indexImage % files.length].getName() );
boardImage.repaint();
}
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Test frame = new Test();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
In reality, no image is shown.
boardImage = new JLabel(icon);
Don't create a new JLabel. The instance of that JLabel hasn't been added to the frame.
Instead just change the Icon of the existing JLabel:
//boardImage = new JLabel(icon);
boardImage.setIcon( icon );
The label will automatically repaint itself with the new Icon.

Dynamically change size of text box JTextArea

I'm new to Java and I've been trying to build a simple instant messaging program. I would like the text box (the place where you type your messages before sending them) to dynamically change size (w/ a max height constraint) as a person enters in a large message, kinda like what happens on WhatsApp or iMessage.
I've tried to count the number of lines of text that are in the text box (taking into account the effects of text wrapping), and then increase/decrease the text box's height according to the number of text-wrapped lines present. I determined the height of 1 row of text using the getScrollableUnitIncrement() method.
Also, as I am learning, is there a better method of dynamically changing the size of the text box than the one I've outlined above?
I used a JTextArea embedded in a JScrollPane and I'm using a componentListener on the JTextArea to resize the textbox as appropriate.
Please check out the while loops in the component listener method as I don't think this part of program works properly...
Here's a snippet of the code:
public class clienterrors extends JFrame {
private JTextArea userText;
private JTextPane chatWindow;
private String userName="testName";
private Document doc;
// Automatic resizing of the text box
private static Dimension textBoxSize = new Dimension(20, 20);
public static int numRows = 1;
private static final int rowHeight = 20;
private final int maxHeight = 80;
public static void main(String[] args) {
clienterrors george = new clienterrors();
george.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public clienterrors(){
super("Client instant messaging platform");
// Chat window initialisation
chatWindow = new JTextPane();
chatWindow.setEditable(false);
doc = chatWindow.getDocument();
add(new JScrollPane(chatWindow), BorderLayout.CENTER);
// Text box initialisation
userText = new JTextArea();
userText.setLineWrap(true);
userText.setWrapStyleWord(true);
JScrollPane jsp = new JScrollPane(userText);
jsp.setPreferredSize(textBoxSize);
jsp.setMaximumSize(new Dimension(20, 40));
// Gets the text box to resize as appropriate
userText.addComponentListener(
new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
// Needs to cater for when removing & pasting large messages into the text box
while (countLines(userText) > numRows && textBoxSize.getHeight() < maxHeight) {
textBoxSize.setSize(20, (int) textBoxSize.getHeight() + rowHeight);
revalidate();
numRows++; // numRows is used as an update to see which
}
while (countLines(userText) < numRows && textBoxSize.getHeight() > 20){
textBoxSize.setSize(20, (int)textBoxSize.getHeight() - rowHeight);
revalidate();
numRows--;
}
}
}
);
// Allows u to send text from text box to chat window
userText.addKeyListener(
new KeyAdapter() {
#Override
public void keyTyped(KeyEvent e) {
if(e.getKeyChar() == '\n' && enterChecker(userText.getText())){
// returns the text (-1 on the substring to remove the \n escape character when pressing enter)
showMessage("\n" + userName + ": " + userText.getText().substring(0, userText.getText().length() - 1));
userText.setText("");
}
}
}
);
add(jsp, BorderLayout.SOUTH);
//JFrame properties
setSize(300, 400);
setVisible(true);
}
// shows message on the chat window
private void showMessage(final String text){
SwingUtilities.invokeLater(
new Runnable() {
#Override
public void run() {
try{
doc.insertString(doc.getLength(), text, null);
}catch(BadLocationException badLocationException){
badLocationException.printStackTrace();
}
// place caret at the end (with no selection), so the newest message can be automatically seen by the user
chatWindow.setCaretPosition(chatWindow.getDocument().getLength());
}
}
);
}
// Prevents the user from sending empty messages that only contain whitespace or \n
private static boolean enterChecker(String t){
for(int i=0; i<t.length(); i++)
if (t.charAt(i) != '\n' && t.charAt(i) != ' ')
return true;
return false;
}
// This counts the number of wrapped lines in the text box to compare to numRows - only used to resize the text box
// (got this off the internet)
private static int countLines(JTextArea textArea) {
if(!enterChecker(textArea.getText())) return 0; // this prevents getting an error when you're sending an empty message
AttributedString text = new AttributedString(textArea.getText());
FontRenderContext frc = textArea.getFontMetrics(textArea.getFont())
.getFontRenderContext();
AttributedCharacterIterator charIt = text.getIterator();
LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(charIt, frc);
float formatWidth = (float) textArea.getSize().width;
lineMeasurer.setPosition(charIt.getBeginIndex());
int noLines = 0;
while (lineMeasurer.getPosition() < charIt.getEndIndex()) {
lineMeasurer.nextLayout(formatWidth);
noLines++;
}
return noLines;
}
}
A JTextArea automatically updates its preferred size so that all text can be displayed. If you don't wrap it in a ScrollPane, the BorderLayout will automatically display what you want without any additional logic:
public class ClientErrors extends JFrame {
private JTextArea userText;
private JTextPane chatWindow;
private String userName = "testName";
// Automatic resizing of the text box
public static int numRows = 1;
private static final int rowHeight = 20;
private final int maxHeight = 80;
private Document doc;
public static void main(String[] args) {
ClientErrors george = new ClientErrors();
george.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public ClientErrors() {
super("Client instant messaging platform");
// Chat window initialisation
chatWindow = new JTextPane();
chatWindow.setEditable(false);
doc = chatWindow.getDocument();
add(new JScrollPane(chatWindow), BorderLayout.CENTER);
// Text box initialisation
userText = new JTextArea();
userText.setLineWrap(true);
userText.setWrapStyleWord(true);
// Allows u to send text from text box to chat window
userText.addKeyListener(new KeyAdapter() {
#Override
public void keyTyped(KeyEvent e) {
if (e.getKeyChar() == '\n' && enterChecker(userText.getText())) {
// returns the text (-1 on the substring to remove the \n
// escape character when pressing enter)
showMessage("\n"
+ userName
+ ": "
+ userText.getText().substring(0,
userText.getText().length() - 1));
userText.setText("");
}
}
});
add(userText, BorderLayout.SOUTH);
// JFrame properties
setSize(300, 400);
setVisible(true);
}
// shows message on the chat window
private void showMessage(final String text) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
doc.insertString(doc.getLength(), text, null);
} catch (BadLocationException badLocationException) {
badLocationException.printStackTrace();
}
// place caret at the end (with no selection), so the newest
// message can be automatically seen by the user
chatWindow.setCaretPosition(chatWindow.getDocument()
.getLength());
}
});
}
// Prevents the user from sending empty messages that only contain
// whitespace or \n
private static boolean enterChecker(String t) {
for (int i = 0; i < t.length(); i++)
if (t.charAt(i) != '\n' && t.charAt(i) != ' ')
return true;
return false;
}
}
EDIT: If you also want a maximum height for your input JTextArea, which scrolls after a maximum height has been reached, I would suggest wrapping it in a scroll pane and updating its preferred size when the text area's changes.

How to Observe JScrollPane end of viewport? [duplicate]

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()

How to know if a JScrollBar has reached the bottom of the JScrollPane?

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()

Categories