Вопрос

У меня есть следующее «дерево» объектов:

JPanel
    JScrollPane
        JPanel
            JPanel
                JScrollPane
                    JTextPane

При использовании колеса мыши для прокрутки внешнего JScrollPane я столкнулся с одной досадной проблемой.Как только курсор мыши касается внутреннего JScrollPane, кажется, что события прокрутки передаются в этот JScrollPane и больше не обрабатываются первым.Это означает, что прокрутка «родительского» JScrollPane прекращается.

Можно ли отключить только колесо мыши на внутренней JScrollPane?Или, что еще лучше, отключите прокрутку, если прокручивать нечего (большую часть времени текстовая панель содержит только 1-3 строки текста), но включите ее. если есть еще контент?

Это было полезно?

Решение

Я тоже столкнулся с этой досадной проблемой, и решение 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());
    }
}
}

Другие советы

К сожалению, очевидное решение (JScrollPane.setWheelScrollingEnabled(false)) фактически не отменяет регистрацию для MouseWheelEvents, поэтому оно не дает желаемого эффекта.

Вот грубый хакерский способ вообще отключить прокрутку, который позволит MouseWheelEvents достигать внешнего JScrollPane:

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

Если вы сделаете это со своим внутренним JScrollPane, он никогда не будет реагировать на события колеса прокрутки;внешний JScrollPane получит их все.

Если вы хотите сделать это «чисто», вам нужно будет реализовать свой собственный ScrollPaneUI и установить его как пользовательский интерфейс JScrollPane с помощью setUI().К сожалению, вы не можете просто расширить BasicScrollPaneUI и отключить его прослушиватель колеса мыши, поскольку соответствующие переменные-члены являются частными, и при установке ScrollPaneUI его MouseWheelListener нет никаких флагов или защитных мер.

Для вашего «еще лучшего» решения вам придется копнуть глубже, чем у меня есть время, в ScrollPaneUI, найти крючки, в которых полосы прокрутки становятся видимыми/невидимыми, и добавить/удалить свой MouseWheelListener в этих точках.

Надеюсь, это поможет!

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

Вдохновленный существующими ответами, я

  • взял код из Ответ Неми
  • объединил это с ответ клеопатры на аналогичный вопрос чтобы избежать построения 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));

После того как экземпляр этого класса создан и привязан к панели прокрутки, его нельзя повторно использовать для другого компонента, поскольку он запоминает предыдущее положение вертикальной полосы прокрутки.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top