Search code examples
javaswingjpaneljscrollpanejtextarea

Prevent scrollbars disappearing when exiting component inside the JScrollPane


I made a JPanel that contains a JScrollPane that surrounds a JTextArea. I want to hide the scrollbars when the mouse exits the JScrollPane.

If I set the MouseListener on the JScrollPane, then nothing happens. If I set it on the JTextArea then the scrollbars hide/unhide correctly, but you cannot click them because they are outside the bounds of the JTextArea.\

How can I prevent the scrollbars from disappearing when you try to click them?

Code example 1. Hiding and unhiding + changing transparency works.

Problem: You cannot click the scrollbars, they disappear because you exit the JTextArea.

public class TransparentTextArea extends JTextArea {

private static final Color LIGHT_TRANSPARENT = new Color(0, 0, 0, 150);
private static final Color HEAVY_TRANSPARENT = new Color(0, 0, 0, 50);

public TransparentTextArea(final GameModel model) {
    setOpaque(false);
    setForeground(Color.WHITE);
    setBackground(HEAVY_TRANSPARENT);
    setEditable(false);
    addMouseListener(new MouseListener() {

        @Override
        public void mouseReleased(MouseEvent e) {
            // TODO Auto-generated method stub

        }

        @Override
        public void mousePressed(MouseEvent e) {
            // TODO Auto-generated method stub

        }

        @Override
        public void mouseExited(MouseEvent e) {
            model.setLogActive(false);
            setBackground(HEAVY_TRANSPARENT);
        }

        @Override
        public void mouseEntered(MouseEvent e) {
            model.setLogActive(true);
            setBackground(LIGHT_TRANSPARENT);
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            // TODO Auto-generated method stub

        }
    });
}
}

public class Logger extends JScrollPane implements PanelObserver,
    ChangeListener {

private GameModel model;

public Logger(final GameModel model) {
    super(new TransparentTextArea(model));
    this.model = model;
    setOpaque(false);
    getViewport().setOpaque(false);
    setBorder(BorderFactory.createEmptyBorder());
    setViewportBorder(BorderFactory.createEmptyBorder());
    model.addPanelObserver(this);
    model.addChangeListener(this);
}

@Override
public void panelResized() {
    float x = model.getPanelWidth() * 0.01f;
    float y = model.getPanelHeight() * 0.70f;
    float width = model.getPanelWidth() * 0.30f;
    float height = model.getPanelHeight() * 0.28f;

    setBounds((int) x, (int) y, (int) width, (int) height);
}

@Override
public void stateChanged(ChangeEvent e) {
    if (model.isLogActive()) {
        setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    } else {
        setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
        setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
    }
}
}

Code example 2.

Problem I tried to fix this by creating a Panel around it and using that MouseExited, but it does not work since the childs consume it.

public class Logger extends JPanel implements PanelObserver, MouseListener {

private static final Color LIGHT_TRANSPARENT = new Color(0, 0, 0, 150);
private static final Color HEAVY_TRANSPARENT = new Color(0, 0, 0, 50);

private GameModel model;
private JTextArea textArea;
private JScrollPane scrollPane;

public Logger(GameModel model) {
    super(null);

    this.model = model;
    setOpaque(false);

    textArea = new JTextArea();
    textArea.setOpaque(false);
    textArea.setForeground(Color.WHITE);
    textArea.setBackground(HEAVY_TRANSPARENT);
    textArea.setEditable(false);

    scrollPane = new JScrollPane(textArea);
    scrollPane.setOpaque(false);
    scrollPane.getViewport().setOpaque(false);
    scrollPane.setBorder(BorderFactory.createEmptyBorder());
    scrollPane.setViewportBorder(BorderFactory.createEmptyBorder());
    add(scrollPane);

    model.addPanelObserver(this);
    addMouseListener(this);
}

@Override
public void panelResized() {
    float x = model.getPanelWidth() * 0.01f;
    float y = model.getPanelHeight() * 0.70f;
    float width = model.getPanelWidth() * 0.30f;
    float height = model.getPanelHeight() * 0.28f;

    setBounds((int) x, (int) y, (int) width, (int) height);
    scrollPane.setBounds(0, 0, (int) width, (int) height);
}

@Override
public void mouseClicked(MouseEvent e) {}

@Override
public void mousePressed(MouseEvent e) {}


@Override
public void mouseReleased(MouseEvent e) {}

@Override
public void mouseEntered(MouseEvent e) {
    scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
    scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    textArea.setBackground(LIGHT_TRANSPARENT);
}

@Override
public void mouseExited(MouseEvent e) {
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        textArea.setBackground(HEAVY_TRANSPARENT);
}
}

Solution

  • Just tried this and it works fine:

     textArea.addMouseMotionListener(new MouseMotionAdapter() {
                    @Override
                    public void mouseMoved(MouseEvent arg0) {
                        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
                        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
                    }
                });
    
     scrollPane.addMouseListener(new MouseAdapter() {
    
                @Override
                public void mouseExited(MouseEvent e) {
    
                    scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
                    scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
                }
            });
    

    The idea is that as long as the mouse is moving within the textArea, the ScrollBar is visible and works fine. As soon as it moves outside the ScrollPane it's not visible anymore.