为什么JScrollPane不响应鼠标滚轮事件?

15

我有一个包含一个使用BoxLayout(PAGE AXIS)的面板的JScrollPane

我的问题是,JScrollPane不会响应鼠标滚轮事件。为了使用鼠标滚轮滚动,我需要在JScrollBar上。

我在这个线程中找到了它,并且没有MouseMotionListenerMouseWheelListener,只有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
请提供一个 SSCCE 来演示问题。 - kleopatra
你能发一下生成这些组件的代码吗?我无法重现你的问题... - 1r0n1k
+1 靠近 SSCCE(你忘了字段声明,它不能编译 :-)) - kleopatra
1
@kleopatra 说实话,这是我第一次尝试编写一个SSCE。我犯了一个错误,因为我故意删除了字段声明,因为我认为它们是无用的。再次阅读SSCCE HOWTO后,我意识到我错了:D - nathan
最简单的解决方案,不会影响/破坏任何其他组件:在此输入链接描述 - JayC667
2个回答

15

沃尔特已经先我分析了这个问题:-)

稍微补充一下:

正确的是,JScrollPane支持鼠标滚轮处理。根据鼠标事件分派规则,最上层(在z-order中)的组件会接收到事件,而这就是围绕文本区域的滚动窗格。因此,如果不需要滚动文本区域,一个简单的解决方案可能是禁用其滚动窗格中的滚轮支持。而JScrollPane甚至有api来实现它:

scrollPane.setWheelScrollingEnabled(false); 

很遗憾,这并不起作用。原因是该属性在事件分发链中没有效果,最终调用eventTypeEnabled函数时无效。

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

如果安装了 mouseWheelListener,则此操作将返回 true - BasicScrollPaneUI 无条件执行此操作,而且在更改 wheelEnabled 属性时不会删除该操作(ui 甚至不会监听该属性...)如果该属性为 false,则侦听器什么都不做。 这些事实中至少有一个是错误的,ui 应该

  • 根据 wheelEnabled 删除/添加侦听器
  • 或者:实现侦听器,使其将事件分派到链上(就像 Walter 在他的示例中所做的那样)

第一种选择可以由应用程序代码处理:

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

这有点像一个黑客技巧(因为bug的解决方法总是如此:-),生产代码必须监听wheelEnable以便在需要时重新安装,并监听LAF更改以更新/删除由UI安装的侦听器。

通过对JScrollPane进行子类化并在wheelEnabled为false时将事件派发到父级来实现第二个选项的轻微修改(如Walter的调度):

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

7
鼠标滚轮事件被文本区域周围的滚动窗格所消耗。您可以尝试手动将该事件传递给父级滚动窗格,如下所示:
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);
            }
        });
    }
}

2
个人而言,我不会费心去发送父级侦听器的消息——简单的调度应该就足够了(一旦滚动窗格决定不自己处理它)。 - kleopatra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接