【问题标题】:Why JScrollPane does not react to mouse wheel events?为什么 JScrollPane 不对鼠标滚轮事件做出反应?
【发布时间】:2012-10-16 09:26:18
【问题描述】:

我有一个JScrollPane,其中包含一个带有BoxLayout(页轴)的面板。

我的问题是 JScrollPane 对鼠标滚轮事件没有反应。要使用鼠标滚轮滚动,我需要在JScrollBar 上。

我找到了这个thread,但我没有MouseMotionListenerMouseWheelListener,只有MouseListener。我认为我的问题来自这样一个事实,即我的JScrollPane 作用于包含其他面板本身的JPanel。因此,当鼠标位于JScrollPane 内的面板上时,似乎该面板消耗了该事件,而滚动面板从未见过该事件。

是否有一种正确的方法可以使滚动窗格的子级捕获的事件对此滚动窗格可见?

SSCCE:

这是一个简单的测试用例,试图显示我何时尝试在我的 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 似乎消耗了该事件,因为当鼠标光标在其中时,使用滚轮的滚动不起作用。我必须将鼠标光标放在文本区域之外才能使其再次工作。

【问题讨论】:

  • 请出示证明问题的 SSCCE
  • 你能发布你用来生成这些组件的代码吗?我无法重现您的问题...
  • +1 用于近 SSCCE(由于您忘记了字段声明,因此无法编译 :-)
  • @kleopatra 老实说,这是我第一次尝试编写 SSCE。我犯了一个错误,因为我故意删除了字段声明,因为我认为它们没有用。再次阅读 SSCCE HOWTO 后,我意识到我错了:D
  • 最简单的解决方案,不会影响/破解任何其他组件:enter link description here

标签: swing events mouseevent jscrollpane


【解决方案1】:

Walter 打败了我来分析这个问题 :-)

添加一点细节:

JScrollPane 支持 mouseWheelHandling 是正确的。根据 mouseEvent 调度规则,最顶层(按 z 顺序)的组件获取事件,即 textArea 周围的 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;
      }

如果安装了 mouseWheelListener,则返回 true - 这是由 BasicScrollPaneUI 无条件完成的,并且 not 在 wheelEnabled 属性更改时删除(用户界面甚至不听该属性...)另外,如果属性为假,侦听器将不执行任何操作。这些事实中至少有一个是错误,用户界面应该

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

第一个选项可以由应用程序代码处理:

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

这有点像 hack(因为 bug 变通办法总是 :-),生产代码必须听 wheelEnable 以在需要时重新安装,并听 LAF 更改以更新/重新删除由安装的侦听器用户界面。

通过对 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); 

【讨论】:

    【解决方案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);
                }
            });
        }
    }
    

    【讨论】:

    • 就我个人而言,我不会打扰父母的听众——一个简单的调度就足够了(一旦 scrollPane 决定不自己处理它)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多