5

次のオブジェクトの「ツリー」があります。

JPanel
    JScrollPane
        JPanel
            JPanel
                JScrollPane
                    JTextPane

マウスホイールを使用して外側のJScrollPaneをスクロールすると、1つの厄介な問題が発生します。マウスカーソルが内側のJScrollPaneに触れるとすぐに、スクロールイベントがそのJScrollPaneに渡され、最初のイベントによって処理されなくなったようです。これは、「親」JScrollPaneのスクロールが停止することを意味します。

内側のJScrollPaneでマウスホイールのみを無効にすることは可能ですか?または、スクロールするものがない場合はスクロールを無効にします(ほとんどの場合、テキストペインには1〜3行のテキストしか含まれていません)が、コンテンツが多い場合は有効にしますか?

4

4 に答える 4

13

私もこの厄介な問題に遭遇しました.テーブルとJTextAreas内をスクロールできるようにする必要があったため、Sboddのソリューションは受け入れられませんでした. スクロール可能なコントロールの上にマウスを置くと、コントロールが底をつくまでそのコントロールをスクロールし、その後、通常はページ全体のスクロールペインである親スクロールペインをスクロールし続けるブラウザと同じ動作にしたかったのです。

このクラスはまさにそれを行います。通常の JScrollPane の代わりに使用してください。お役に立てば幸いです。

/**
 * 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());
    }
}
}
于 2009-09-04T14:41:40.747 に答える
2

既存の回答に触発されて、私は

  • ネミの回答からコードを取得しました
  • 冗長な構成を避けるために、同様の質問に対するクレオパトラの回答MouseWheelEventと組み合わせました
  • JScrollPaneクラスを拡張できないコンテキストで使用できるように、リスナーを独自の最上位クラスに抽出しました
  • コードを可能な限りインライン化しました。

結果は次のコードです。

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();
    }
}

次のように使用されます。

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

このクラスのインスタンスが作成され、スクロール ペインにバインドされると、垂直スクロール バーの以前の位置が記憶されているため、別のコンポーネントで再利用することはできません。

于 2018-12-08T21:11:14.890 に答える
1

悲しいことに、明らかな解決策 (JScrollPane.setWheelScrollingEnabled(false)) は実際には MouseWheelEvents の登録を解除しないため、必要な効果が得られません。

これは、MouseWheelEvents が外側の JScrollPane に到達できるようにする、スクロールを完全に無効にする大雑把な方法です。

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

内部の JScrollPane に対してこれを行うと、スクロール ホイール イベントに応答しなくなります。外側の JScrollPane はそれらすべてを取得します。

「きれいに」実行したい場合は、独自の ScrollPaneUI を実装し、それを setUI() で JScrollPane の UI として設定する必要があります。残念ながら、BasicScrollPaneUI を拡張してそのマウス ホイール リスナーを無効にすることはできません。これは、関連するメンバー変数がプライベートであり、ScrollPaneUI の MouseWheelListener のインストールにフラグやガードがないためです。

「さらに優れた」ソリューションを得るには、ScrollPaneUI を深く掘り下げ、スクロールバーを表示/非表示にするフックを見つけ、それらのポイントで MouseWheelListener を追加/削除する必要があります。

それが役立つことを願っています!

于 2009-09-04T14:26:32.237 に答える