Skip to content
Advertisement

Java JScrollPane scrolls to bottom… almost

I have a ScrollPane with a JPanel in my Java program that gets alot of text appended. I need the ScrollPane to scroll to the very bottom after every addition. My problem with the following code is that it scrolls “almost” to the bottom but not all the way. Any ideas as to what I am doing wrong?

import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;

public class Sample extends JFrame{
    JTextField input = new JTextField();
    JPanel log = new JPanel();
    JScrollPane scrollPane = new JScrollPane(log);
    JScrollBar scrollBar = scrollPane.getVerticalScrollBar();

    public Sample(){

        input.setBorder(new TitledBorder("Message"));
        input.setToolTipText("Type a message here.");

        log.setBorder(new TitledBorder("Log"));
        log.setLayout(new BoxLayout(log, BoxLayout.Y_AXIS));
        log.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

        // Add the panels to the frame
        setLayout(new BorderLayout(10, 10));
        add(input, BorderLayout.NORTH);
        add(scrollPane, BorderLayout.CENTER);

        setTitle("Chat Window");
        setSize(400, 300);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);

        // Register Key listener
        input.addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    log.add(new ServerBubble(input.getText()));
                    log.add(Box.createRigidArea(new Dimension(0, 10)));
                    input.setText(null);
                    scrollPane.revalidate();
                    scrollBar.setValue(scrollBar.getMaximum());
                }
            }
        });
    }

    public static void main(String[] args) {
        JFrame frame = new Exercise33_9Server();

    }

    private class ServerBubble extends JLabel{

        private final Color LIGHT_GREEN = new Color(204, 255, 229);
        private final Color DARK_GREEN = new Color(0, 102, 51);
        String message;

        public ServerBubble(String label){
            message = processText(label);
            setOpaque(true);
            setForeground(DARK_GREEN);
            setBackground(LIGHT_GREEN);
            setHorizontalAlignment(LEFT);
            setBorder(new LineBorder(DARK_GREEN, 3, true));
            setText(label);
            setBorder(BorderFactory.createCompoundBorder(getBorder(), BorderFactory.createEmptyBorder(5, 5, 5, 5)));
        }

        private String processText(String message){
            message = "<HTML>" + message;
            return message;
        }
    }
}

Advertisement

Answer

Basically, you may be trying to update the viewport before the component hierarchy has had time to update. Sometimes, you just have to wait.

This example basically uses SwingUtilities.invokeLater to put a request onto the Event Queue that some body of work should be performed “at some time in the near future”. This allows the current pending updates time to be processed…

if (e.getKeyCode() == KeyEvent.VK_ENTER) {

    log.add(new ServerBubble(input.getText()));
    log.add(Box.createRigidArea(new Dimension(0, 10)));
    input.setText(null);
    log.revalidate();

    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            Dimension vpSize = scrollPane.getViewport().getExtentSize();
            Dimension logSize = log.getSize();

            int height = logSize.height - vpSize.height;
            scrollPane.getViewport().setViewPosition(new Point(0, height));
        }
    });
}

At which time, I grab the current size of the view and compare it to the current size of the log panel, I then use the different to calculate the y-offset from the bottom of the panel which would allow the last element to be visible…

Advertisement