views:

754

answers:

2

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?

A: 

Sadly, the obvious solution (JScrollPane.setWheelScrollingEnabled(false)) doesn't actually deregister for MouseWheelEvents, so it doesn't achieve the effect you want.

Here's a crude-hackery way of disabling scrolling altogether that will let the MouseWheelEvents reach the outer JScrollPane:

for (MouseWheelListener mwl : scrollPane.getMouseWheelListeners()) {
  scrollPane.removeMouseWheelListener(mwl);
}

If you do this to your inner JScrollPane, it'll never respond to scroll wheel events; the outer JScrollPane will get all of them.

If you want to do it "cleanly", you'd need to implement your own ScrollPaneUI, and set that as the JScrollPane's UI with setUI(). Unfortunately, you can't just extend BasicScrollPaneUI and disable its mouse wheel listener, because the relevant member variables are private and there aren't any flags or guards on the ScrollPaneUI's installation of its MouseWheelListener.

For your "even better" solution, you'd have to dig deeper than I have time to into the ScrollPaneUI, find the hooks where the scrollbars get made visible / invisible, and add/remove your MouseWheelListener at those points.

Hope that helps!

Sbodd
+3  A: 

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());
    }
}
}
Nemi
Thanks, This behaves exactly the way I wanted it.
exhuma
Thanks a bunch, works like a champ!
Satish