Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JTextPane prevents scrolling in the parent JScrollPane

I have the following "tree" of objects:

JPanel
    JScrollPane
        JPanel
            JPanel
                JScrollPane
                    JTextPane

When using the mouse wheel to scroll over the outer JScrollPane I encounter one annoying problem. As soon as the mouse cursor touches the inner JScrollPane, it seems that the scrolling events get passed into that JScrollPane and are not processed anymore by the first one. That means that scrolling the "parent" JScrollPane stops.

Is it possible to disable only the mouse wheel on the inner JScrollPane? Or even better, disable scrolling if there is nothing to scroll (most of the time the textpane only contains 1-3 lines of text), but enable it if there is more content?

like image 500
exhuma Avatar asked Sep 04 '09 08:09

exhuma


People also ask

How do I get rid of JScrollPane?

If you want to remove this whole panel, You need to remove it from the JFrame . So replace this statement with: frame. remove(game1);

What is the difference between JScrollPane and scrollbar?

What are the differences between a JScrollBar and a JScrollPane in Java? A JScrollBar is a component and it doesn't handle its own events whereas a JScrollPane is a Container and it handles its own events and performs its own scrolling.

How do I make my JScrollPane scroll faster?

Just use the reference to your JScrollPane object, get the vertical scroll bar from it using getVerticalScrollBar , and then call setUnitIncrement on it, like this: myJScrollPane. getVerticalScrollBar().

What is JScrollPane in Java Swing?

A JScrollPane provides a scrollable view of a component. When screen real estate is limited, use a scroll pane to display a component that is large or one whose size can change dynamically. Other containers used to save screen space include split panes and tabbed panes.


2 Answers

I have run into this annoying problem also, and Sbodd's solution was not acceptable for me because I needed to be able to scroll inside tables and JTextAreas. I wanted the behavior to be the same as a browser, where the mouse over a scrollable control will scroll that control until the control bottoms out, then continue to scroll the parent scrollpane, usually the scrollpane for the whole page.

This class will do just that. Just use it in place of a regular JScrollPane. I hope it helps you.

/**
 * A JScrollPane that will bubble a mouse wheel scroll event to the parent 
 * JScrollPane if one exists when this scrollpane either tops out or bottoms out.
 */
public class PDControlScrollPane extends JScrollPane {

public PDControlScrollPane() {
    super();

    addMouseWheelListener(new PDMouseWheelListener());
}

class PDMouseWheelListener implements MouseWheelListener {

    private JScrollBar bar;
    private int previousValue = 0;
    private JScrollPane parentScrollPane; 

    private JScrollPane getParentScrollPane() {
        if (parentScrollPane == null) {
            Component parent = getParent();
            while (!(parent instanceof JScrollPane) && parent != null) {
                parent = parent.getParent();
            }
            parentScrollPane = (JScrollPane)parent;
        }
        return parentScrollPane;
    }

    public PDMouseWheelListener() {
        bar = PDControlScrollPane.this.getVerticalScrollBar();
    }
    public void mouseWheelMoved(MouseWheelEvent e) {
        JScrollPane parent = getParentScrollPane();
        if (parent != null) {
            /*
             * Only dispatch if we have reached top/bottom on previous scroll
             */
            if (e.getWheelRotation() < 0) {
                if (bar.getValue() == 0 && previousValue == 0) {
                    parent.dispatchEvent(cloneEvent(e));
                }
            } else {
                if (bar.getValue() == getMax() && previousValue == getMax()) {
                    parent.dispatchEvent(cloneEvent(e));
                }
            }
            previousValue = bar.getValue();
        }
        /* 
         * If parent scrollpane doesn't exist, remove this as a listener.
         * We have to defer this till now (vs doing it in constructor) 
         * because in the constructor this item has no parent yet.
         */
        else {
            PDControlScrollPane.this.removeMouseWheelListener(this);
        }
    }
    private int getMax() {
        return bar.getMaximum() - bar.getVisibleAmount();
    }
    private MouseWheelEvent cloneEvent(MouseWheelEvent e) {
        return new MouseWheelEvent(getParentScrollPane(), e.getID(), e
                .getWhen(), e.getModifiers(), 1, 1, e
                .getClickCount(), false, e.getScrollType(), e
                .getScrollAmount(), e.getWheelRotation());
    }
}
}
like image 59
Nemi Avatar answered Sep 19 '22 16:09

Nemi


Inspired by the existing answers, I

  • took the code from Nemi's answer
  • combined it with kleopatra's answer to a similar question to avoid constructing the MouseWheelEvent verbosely
  • extracted the listener into its own top-level class so that it can be used in contexts where the JScrollPane class cannot be extended
  • inlined the code as far as possible.

The result is this piece of code:

import java.awt.Component;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

/**
 * Passes mouse wheel events to the parent component if this component
 * cannot scroll further in the given direction.
 * <p>
 * This behavior is a little better than Swing's default behavior but
 * still worse than the behavior of Google Chrome, which remembers the
 * currently scrolling component and sticks to it until a timeout happens.
 *
 * @see <a href="https://stackoverflow.com/a/53687022">Stack Overflow</a>
 */
public final class MouseWheelScrollListener implements MouseWheelListener {

    private final JScrollPane pane;
    private int previousValue;

    public MouseWheelScrollListener(JScrollPane pane) {
        this.pane = pane;
        previousValue = pane.getVerticalScrollBar().getValue();
    }

    public void mouseWheelMoved(MouseWheelEvent e) {
        Component parent = pane.getParent();
        while (!(parent instanceof JScrollPane)) {
            if (parent == null) {
                return;
            }
            parent = parent.getParent();
        }

        JScrollBar bar = pane.getVerticalScrollBar();
        int limit = e.getWheelRotation() < 0 ? 0 : bar.getMaximum() - bar.getVisibleAmount();
        if (previousValue == limit && bar.getValue() == limit) {
            parent.dispatchEvent(SwingUtilities.convertMouseEvent(pane, e, parent));
        }
        previousValue = bar.getValue();
    }
}

It is used like this:

JScrollPane pane = new JScrollPane();
pane.addMouseWheelListener(new MouseWheelScrollListener(pane));

Once an instance of this class is created and bound to a scroll pane, it cannot be reused for another component since it remembers the previous position of the vertical scroll bar.

like image 25
Roland Illig Avatar answered Sep 20 '22 16:09

Roland Illig