5

我有以下对象“树”:

JPanel
    JScrollPane
        JPanel
            JPanel
                JScrollPane
                    JTextPane

当使用鼠标滚轮滚动外部 JScrollPane 时,我遇到了一个烦人的问题。一旦鼠标光标接触到内部 JScrollPane,滚动事件似乎就被传递到该 JScrollPane 并且不再被第一个处理。这意味着滚动“父” JScrollPane 停止。

是否可以禁用内部 JScrollPane 上的鼠标滚轮?或者更好的是,如果没有可滚动的内容(大多数时候文本窗格只包含 1-3 行文本)禁用滚动,但如果有更多内容则启用它?

4

4 回答 4

13

我也遇到了这个烦人的问题,而 Sbodd 的解决方案对我来说是不可接受的,因为我需要能够在表格和 JTextAreas 内滚动。我希望行为与浏览器相同,鼠标悬停在可滚动控件上将滚动该控件,直到控件触底,然后继续滚动父滚动窗格,通常是整个页面的滚动窗格。

这堂课会做到这一点。只需使用它来代替常规的 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

受现有答案的启发,我

结果是这段代码:

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 回答
1

@Nemi 已经有一个很好的解决方案。

我将其进一步简化,将以下方法放入我的库中:

static public void passMouseWheelEventsToParent(final Component pComponent, final Component pParent) {
        pComponent.addMouseWheelListener((final MouseWheelEvent pE) -> {
            pParent.dispatchEvent(new MouseWheelEvent(pParent, pE.getID(), pE.getWhen(), pE.getModifiers(), 1, 1, pE.getClickCount(), false, pE.getScrollType(), pE.getScrollAmount(), pE.getWheelRotation()));
        });
}
于 2017-07-14T14:31:55.540 回答