【问题标题】:JTextPane prevents scrolling in the parent JScrollPaneJTextPane 防止在父 JScrollPane 中滚动
【发布时间】:2010-11-25 13:27:12
【问题描述】:

我有以下对象“树”:

JPanel
    JScrollPane
        JPanel
            JPanel
                JScrollPane
                    JTextPane

当使用鼠标滚轮滚动外部 JScrollPane 时,我遇到了一个烦人的问题。一旦鼠标光标接触到内部 JScrollPane,滚动事件似乎就被传递到该 JScrollPane 并且不再由第一个处理。这意味着滚动“父”JScrollPane 停止。

是否可以禁用内部 JScrollPane 上的鼠标滚轮?或者更好的是,如果没有可滚动的内容,则禁用滚动(大部分时间文本窗格仅包含 1-3 行文本),但如果有更多内容,则启用它

【问题讨论】:

    标签: java swing user-interface jscrollpane jtextpane


    【解决方案1】:

    遗憾的是,显而易见的解决方案(JScrollPane.setWheelScrollingEnabled(false))实际上并没有为 MouseWheelEvents 注销,所以它并没有达到你想要的效果。

    这是一种完全禁用滚动的粗略黑客方法,它会让 MouseWheelEvents 到达外部 JScrollPane:

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

    如果您对内部 JScrollPane 执行此操作,它将永远不会响应滚轮事件;外部的 JScrollPane 将获取所有这些。

    如果你想“干净”地做到这一点,你需要实现自己的 ScrollPaneUI,并使用 setUI() 将其设置为 JScrollPane 的 UI。不幸的是,您不能仅扩展 BasicScrollPaneUI 并禁用其鼠标滚轮侦听器,因为相关的成员变量是私有的,并且 ScrollPaneUI 安装其 MouseWheelListener 时没有任何标志或保护。

    对于“更好”的解决方案,您必须比我有时间深入挖掘 ScrollPaneUI,找到使滚动条可见/不可见的钩子,并在这些点添加/删除 MouseWheelListener。

    希望有帮助!

    【讨论】:

      【解决方案2】:

      我也遇到了这个烦人的问题,我无法接受 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());
          }
      }
      }
      

      【讨论】:

      • 谢谢,这正是我想要的方式。
      【解决方案3】:

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

      【讨论】:

      • 但是这段代码不是同时滚动两个组件吗?我觉得这很令人困惑。
      • 由于 Sbodd、Nemi、exhuma 等状态以及该主题的来源,JTextPane 拒绝将事件传递给周围的 JScrollPane。所以,使用这个,只有 JScrollPane 会滚动......另一方面,其他的解决方案更好,即 Nemi 和 Sbodd 的解决方案。
      【解决方案4】:

      受现有答案的启发,我

      • Nemi's answer获取代码
      • 将其与kleopatra's answer to a similar question 结合使用,以避免冗长地构造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));
      

      一旦创建了此类的实例并将其绑定到滚动窗格,它就不能再用于另一个组件,因为它会记住垂直滚动条的先前位置。

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-17
      • 1970-01-01
      • 2012-11-03
      相关资源
      最近更新 更多