简体   繁体   中英

Java ScrollPane interactive scrolling

I am building an chat app where I add chats to an JPanel attached to a JScrollPane . Chats can be added and removed from my JPanel and as that happens I want my JScrollPane to perform 3 functions

  1. If the vertical scrollBar is not visible (ie no need for scrolling due to very little chats on screen) and when an new chat enters the panel causing the scrollBar to become visible for the first time then the scrollBar must scroll to it's maximum value so as to make the newly added chat visible

  2. If the user has scrolled to the maximum value/very bottom of the scrollPane as physically possible and when an new chat enters the panel causing the scrollBar 's maximum value to go up then perform the same as Action 1

  3. If the user has scrolled up the chat to see previous messages (ie to some random point which is LESS than the MAXIMUM value of the scrollBar ) and then some new chat appears then a scroll button must appear in the chat and if the user clicks on it the scrollBar scrolls to the NEW MAXIMUM value so as to make the newly added chats visible

Basically my algorithm is as follows

//The user is currently at the bottom of the chat or scrollBar is not visible therefore making current & maximum value = 0
if (scrollBar.currentValue() == scrollBar.maxValue()) {
    scrollButton.setVisible(false);
    guiUpdate();  //Then Add,Remove The Chat
    scrollToVeryButton();
}
else {
    //The user is somewhere top of the chat looking at previous messages
    guiUpdate();   //Then Add,Remove The Chat
    scrollButton.setVisible(true); //Upon clicking scrollToVeryButton(); Is Called
}

This algorithm unfortunatly dosen't work and the scrollButton always ends up visible regardless. Here Is the full code:

Chat Display class for displaying chats


import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;

class ChatDisplay extends JLayeredPane {

    private JScrollPane scrollPane;
    private JPanel chatDisplay;
    private Scroll notify;

    ChatDisplay() {
        setLayout(null);
        addChatDisplay();
        addNotifyButton();
    }

    private void addChatDisplay() {
        chatDisplay = new JPanel();
        chatDisplay.setLayout(new BoxLayout(chatDisplay, BoxLayout.Y_AXIS));
        scrollPane = new JScrollPane(chatDisplay);
        scrollPane.setBounds(0, 0, 300, 300);
        add(scrollPane, new Integer(0));
    }

    private void addNotifyButton() {
        notify = new Scroll();
        notify.setBounds(150, 150, 80, 50);
        add(notify, new Integer(1));
        notify.setVisible(false);
    }

    //Called To Add An New Chat
    void addButton(String text) {
        guiUpdate(() -> {
            chatDisplay.add(new Chat(text));
            chatDisplay.revalidate();
            chatDisplay.repaint();
        });
    }

    //The general update when gui is changed in anyway
    private void guiUpdate(Runnable update) {
        JScrollBar scrollBar = scrollPane.getVerticalScrollBar();
        if (scrollBar.getValue() == scrollBar.getMaximum()) {
            notify.setVisible(false);
            update.run();
            scrollBar.setValue(scrollBar.getMaximum());
        } else {
            update.run();
            notify.setVisible(true);
        }
    }

    //Called To Remove An Chat
    private void removeButton(JButton button) {
        guiUpdate(() -> {
            chatDisplay.remove(button);
            chatDisplay.revalidate();
            chatDisplay.repaint();
        });
    }

    //The Chat Button
    private final class Chat extends JButton implements ActionListener {

        private Chat(String text) {
            setText(text);
            Dimension dim = new Dimension(300, 100);
            setPreferredSize(dim);
            setMinimumSize(dim);
            setMaximumSize(dim);
            addActionListener(Chat.this);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            removeButton(Chat.this); //Upon Clicking Remove Itself From Chat
        }
    }

    // The ScrollBar
    private final class Scroll extends JButton implements ActionListener {

        private Scroll() {
            setText("Scroll");
            setBackground(Color.green);
            addActionListener(Scroll.this);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            JScrollBar scrollBar = scrollPane.getVerticalScrollBar();
            scrollBar.setValue(scrollBar.getMaximum());  //Scroll To The Bottom Upon Clicking
            setVisible(false);  //Its Job Is Done Wait For Next Event
        }
    }
}

ChatPanel set as content pane

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JPanel;

class ChatPanel extends JPanel implements ActionListener {
    private ChatDisplay display;
    private int chat;

    ChatPanel() {
        super(null);
        addDisplay();
        addButton();
    }

    private void addDisplay() {
        display = new ChatDisplay();
        display.setBounds(0, 0, 300, 300);
        add(display);
    }

    private void addButton() {
        JButton add = new JButton("ADD CHAT");
        add.addActionListener(this);
        add.setBounds(0, 310, 300, 30);
        add(add);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        display.addButton("Chat " + (++chat));
    }
}

Main Class

import javax.swing.JFrame;

public class Main {
    public static void main(String args[]) throws Exception {
        JFrame frame = new JFrame("Design Frame");
        frame.setContentPane(new ChatPanel());
        frame.setSize(320, 380);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
        frame.setVisible(true);
    }
}

I have found the answer

The Solution

The key is to make the scrollBar always scroll to the bottom whenever there are any "ADJUSTMENTS"[add/remove an chat] made to it and it must execute this behavior only if the user has previously scrolled to the maximum value

 scrollBar.addAdjustmentListener((event)->
 {
   if(autoScroll)//an boolean flag which is set to true whenever an chat is added/removed 
   {
    scrollBar.setValue(scrollBar.getMaximum());
    
    autoScroll=false; //set to false so the user can scroll freely upwards otherwise it will always reset it to the amaximum value
   }
 });

Fixes

current=scrollBar.getValue() 

will never be equal to maximum unless it is added to the visible amount it is displaying on screen ie

currentValue=scrollBar.getValue()+scrollBar.getVisibleAmount()

The solution & fix were implemented in the guiUpdate() method

FullCode

 private boolean autoScroll;
 private void guiUpdate(Runnable update)
 {
  JScrollBar scrollBar=scrollPane.getVerticalScrollBar();
  
  scrollBar.addAdjustmentListener((event)->
  {
   if(autoScroll)
   {
    scrollBar.setValue(scrollBar.getMaximum());
    
    autoScroll=false;
   }
  });
  
 
  if(scrollBar.getValue()+scrollBar.getVisibleAmount()==scrollBar.getMaximum())
  {
   notify.setVisible(false);
     
   autoScroll=true;  //The user is at the bottom of the screen hence autoScroll to the new maximum
   
   update.run();
  }
  else
  {
   autoScroll=false;  //the user is looking at previous chat messages hence don't scroll to the bottom
   
   update.run();
   
   notify.setVisible(true);
  }
 }

Related Posts

  1. JScrollPane scrollToBottom

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM