Почему JScrollPane не реагирует на события колесика мыши?

У меня есть JScrollPane, содержащий панель с BoxLayout (PAGE AXIS).

Моя проблема в том, что JScrollPane не реагирует на события колесика мыши. Чтобы сделать это прокруткой с помощью колеса мыши, я должен быть на JScrollBar.

Я нашел этот поток, и у меня нет MouseMotionListener или MouseWheelListener, только a MouseListener. Я думаю, что моя проблема связана с тем, что мой JScrollPane действует на JPanel, который содержит другие панели. Поэтому, когда мышь находится на панели в JScrollPane, кажется, что событие используется этой панелью, которую я никогда не видел на панели прокрутки.

Есть ли правильный способ сделать события, пойманные дочерними элементами панели прокрутки, видимыми для этой панели прокрутки?

SSCCE:

enter image description here

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

Кадр:

public class NewJFrame extends javax.swing.JFrame {

    public NewJFrame() {
        initComponents();
        for (int i = 0; i < 50; i++) {
            jPanel1.add(new TestPanel());
        }
    }

private void initComponents() {
        jScrollPane1 = new javax.swing.JScrollPane();
        jPanel1 = new javax.swing.JPanel();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jPanel1.setLayout(new javax.swing.BoxLayout(jPanel1,    javax.swing.BoxLayout.PAGE_AXIS));
        jScrollPane1.setViewportView(jPanel1);

        getContentPane().add(jScrollPane1, java.awt.BorderLayout.CENTER);

        pack();
    }

    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
           @Override
            public void run() {
                new NewJFrame().setVisible(true);
           }
        });
    }
}

И определение TestPanel:

public class TestPanel extends javax.swing.JPanel {

    public TestPanel() {
        initComponents();
    }

    private void initComponents() {

        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        jScrollPane1 = new javax.swing.JScrollPane();
        jTextArea1 = new javax.swing.JTextArea();

        jLabel1.setText("jLabel1");

        setBackground(new java.awt.Color(255, 51, 51));
        setLayout(new java.awt.BorderLayout());

        jLabel2.setText("TEST LABEL");
        jLabel2.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        add(jLabel2, java.awt.BorderLayout.PAGE_START);

        jTextArea1.setEditable(false);
        jTextArea1.setColumns(20);
        jTextArea1.setRows(5);
        jTextArea1.setFocusable(false);
        jScrollPane1.setViewportView(jTextArea1);

        add(jScrollPane1, java.awt.BorderLayout.CENTER);
   }
}

Кажется, что JTextArea потребляет событие, так как, когда курсор находится внутри него, прокрутка с использованием колеса не работает. Я должен поместить курсор мыши за пределы области текста, чтобы он снова работал.

Ответ 1

Уолтер избил меня, чтобы проанализировать проблему:-)

Добавление немного деталей:

Правильно, что JScrollPane поддерживает mouseWheelHandling. В соответствии с правилами диспетчеризации mouseEvent, самый верхний (в z-порядке) компонент получает событие и что scrollPane вокруг textArea. Поэтому, если для обработки текстового поля не требуется, простым решением может быть отключить поддержку колеса в его scrollPane. И JScrollPane даже имеет api для этого:

scrollPane.setWheelScrollingEnabled(false); 

К сожалению, это не работает. Причина, по которой он не работает, заключается в том, что это свойство не влияет на цепочку отправки событий, которая в конечном итоге вызывает eventTypeEnabled:

case MouseEvent.MOUSE_WHEEL:
      if ((eventMask & AWTEvent.MOUSE_WHEEL_EVENT_MASK) != 0 ||
          mouseWheelListener != null) {
          return true;
      }

Это возвращает true, если установлен mouseWheelListener - который выполняется без ограничений BasicScrollPaneUI и не удаляется при изменении свойства wheelEnabled (ui даже не прослушивает это свойство...) Кроме того, слушатель просто ничего не делает, если свойство является ложным. По крайней мере один из этих фактов является ошибкой, ui должен

  • удалить/добавить слушателя в зависимости от wheelEnabled
  • или: реализовать слушателя таким образом, чтобы он отправил событие вверх по цепочке (как это делает Уолтер в своем примере)

Первый вариант может быть обработан кодом приложения:

scrollPane = new JScrollPane();
scrollPane.removeMouseWheelListener(scrollPane.getMouseWheelListeners()[0]);

это немного взломать (так как обходные ошибки всегда есть:-), производственный код должен был бы прослушивать wheelEnable для повторной установки при необходимости плюс прослушивать изменения LAF для обновления/повторного удаления слушателей, установленных ui.

Внедрение второго варианта с небольшими изменениями (как для отправки Уолтера) путем подкласса JScrollPane и отправки события родительскому, если значение wheelEnabled равно false:

scrollPane = new JScrollPane() {

    @Override
    protected void processMouseWheelEvent(MouseWheelEvent e) {
        if (!isWheelScrollingEnabled()) {
            if (getParent() != null) 
                getParent().dispatchEvent(
                        SwingUtilities.convertMouseEvent(this, e, getParent()));
            return;
        }
        super.processMouseWheelEvent(e);
    }

};
scrollPane.setWheelScrollingEnabled(false); 

Ответ 2

Событие колеса мыши расходуется на панели прокрутки вокруг текстовой области. Вы можете попытаться вручную передать событие в родительскую панель прокрутки следующим образом:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class TestScrollPane2 {
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                // might want to use a http://tips4java.wordpress.com/2009/12/20/scrollable-panel/
                JPanel panel = new JPanel(new GridLayout(0, 1));
                for (int i = 0; i < 10; i++) {
                    panel.add(new JScrollPane(new JTextArea(3, 40)) {
                         @Override
                        protected void processMouseWheelEvent(MouseWheelEvent e) {
                            Point oldPosition = getViewport().getViewPosition();
                            super.processMouseWheelEvent(e);

                            if(getViewport().getViewPosition().y == oldPosition.y) {
                                delegateToParent(e);
                            }
                        }

                        private void delegateToParent(MouseWheelEvent e) {
                            // even with scroll bar set to never the event doesn't reach the parent scroll frame
                            JScrollPane ancestor = (JScrollPane) SwingUtilities.getAncestorOfClass(
                                    JScrollPane.class, this);
                            if (ancestor != null) {
                                MouseWheelEvent converted = null;
                                for (MouseWheelListener listener : ancestor
                                        .getMouseWheelListeners()) {
                                    listener.mouseWheelMoved(converted != null ? converted
                                            : (converted = (MouseWheelEvent) SwingUtilities
                                                    .convertMouseEvent(this, e, ancestor)));
                                }
                            }
                        }
                    });
                }
                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                frame.getContentPane().add(new JScrollPane(panel));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}