Swing 为什么JScrollPane不会对鼠标滚轮事件做出反应?

Swing 为什么JScrollPane不会对鼠标滚轮事件做出反应?,swing,events,mouseevent,jscrollpane,Swing,Events,Mouseevent,Jscrollpane,我有一个JScrollPane包含一个带有BoxLayout(页面轴)的面板 我的问题是JScrollPane不会对鼠标滚轮事件做出反应。要使用鼠标滚轮使其滚动,我需要在JScrollBar上 我找到了这个,我没有MouseMotionListener或mouseweelllistener,只有一个MouseListener。我认为我的问题来自这样一个事实:我的JScrollPane作用于一个JPanel,该面板本身包含其他面板。因此,当鼠标位于JScrollPane中的一个面板上时,该事件似乎

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

我的问题是JScrollPane不会对鼠标滚轮事件做出反应。要使用鼠标滚轮使其滚动,我需要在
JScrollBar

我找到了这个,我没有
MouseMotionListener
mouseweelllistener
,只有一个
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);
           }
        });
    }
}

JTextArea
似乎在使用该事件,因为当鼠标光标位于其中时,使用滚轮的滚动不起作用。我必须将鼠标光标放在文本区域外,使其再次工作。

鼠标滚轮事件被文本区域周围的滚动窗格使用。您可以尝试手动将事件传递到父滚动窗格,如下所示:

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);
            }
        });
    }
}
import java.awt.*;
导入java.awt.event.*;
导入javax.swing.*;
公共类TestScrollPane2{
公共静态void main(字符串[]args){
invokeLater(新的Runnable(){
@凌驾
公开募捐{
//可能需要使用http://tips4java.wordpress.com/2009/12/20/scrollable-panel/
JPanel面板=新JPanel(新网格布局(0,1));
对于(int i=0;i<10;i++){
添加(新的JScrollPane(新的JTextArea(3,40)){
@凌驾
受保护的无效进程MouseWheelEvent(MouseWheelEvent e){
点oldPosition=getViewport().getViewPosition();
super.processMouseWheelEvent(e);
if(getViewport().getViewPosition().y==oldPosition.y){
委托人(e);
}
}
私人无效委托人(MouseWheele事件){
//即使滚动条设置为“从不”,事件也不会到达父滚动框
JScrollPane祖先=(JScrollPane)SwingUtilities.getAncestorOfClass(
JScrollPane.class,这个);
if(祖先!=null){
MouseWheelEvent converted=null;
for(mouseweelllistener):祖先
.getMouseWheelListeners()){
listener.mouseWheelMoved(已转换!=null?已转换)
:(已转换=(MouseWheelEvent)SwingUtilities
.convertMouseEvent(这个,e,祖先));
}
}
}
});
}
JFrame=新JFrame(“测试”);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane();
frame.pack();
frame.setLocationRelativeTo(空);
frame.setVisible(true);
}
});
}
}

沃尔特在分析问题时击败了我:-)

添加一些细节:

JScrollPane支持mouseWheelHandling是正确的。根据mouseEvent调度的规则,最上面的(按z顺序)组件获取事件,这就是文本区域周围的滚动窗格。因此,如果不需要旋转textarea,一个简单的解决方案可能是在滚动窗格中禁用滚轮支持。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,则侦听器不会执行任何操作。这些事实中至少有一个是bug,ui应该

  • 删除/添加侦听器取决于是否已启用
  • 或者:实现侦听器,使其将事件发送到链的上游(就像Walter在他的示例中所做的那样)
第一个选项可由应用程序代码处理:

scrollPane = new JScrollPane();
scrollPane.removeMouseWheelListener(scrollPane.getMouseWheelListeners()[0]);
这有点像黑客(bug解决方法总是:-),生产代码必须监听wheelEnable以在需要时重新安装,并监听LAF更改以更新/重新删除ui安装的侦听器

通过子类化JScrollPane实现第二个选项(关于Walter的分派),并在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); 

请展示一个SSCCE,演示问题。您可以发布用于生成这些组件的代码吗?我无法复制您的问题…+1用于近SSCCE(它无法编译,因为您忘记了字段声明:-)@kleopatra老实说,这是我第一次尝试编写SSCE。我犯了一个错误,因为我故意删除了字段声明,因为我认为它们是无用的。在再次阅读SSCCE HOWTO之后,我意识到我错了:在不影响/攻击任何其他组件的情况下,DSimplest解决方案:就个人而言,我不会为向家长的侦听器发送消息而烦恼——一个简单的分派应该就行了
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);