【问题标题】:How can I prevent a JSlider from moving full-range?如何防止 JSlider 全方位移动?
【发布时间】:2016-01-02 01:03:23
【问题描述】:

我有一个应用程序,我想使用 JSlider 进行用户输入,代表绝对值的百分比。
基于其他变量,最小/最大百分比值将受到限制。我不想更改JSlider 本身的比例(最小/最大)(我想保留全范围百分比比例)。我创建了一个ChangeListener 来确定是否需要限制范围,如果需要,我使用适当的(有限的最小值/最大值)调用JSlider.setValue() 方法。但是,JSlider 本身并不能反映我设置的值。如果我使用JSlider.getValue() 查询值,它会返回我期望的值(我设置的值)。

有没有办法强制JSlider 只允许将实际滑块(​​拇指、旋钮)拖动到某个点,然后停止?

更新:

在这方面花了太多时间之后,我认为根本问题是我在非预期使用模型中误用/滥用了 JSlider。将拇指的移动范围限制为小于 BoundedRangeModel 范围需要修改 BasicSliderUI 类。虽然下面 aterai 提出的原始解决方案确实有效,但它需要重写 SliderUI 实现类,从而影响 plaf 的可移植性和一致性。因此,要么我必须找到不同的 UI 元素,要么根据其他相互依赖的变量值修改 JSlider BoundedRangeModel 限制。后者的缺点是相同的拇指位置会根据其他用户可编辑参数的值表示不同的值。

【问题讨论】:

  • 您可能需要提供自定义BoundedRangeModel
  • @MadProgrammer 我相信这实际上是我创造的。 ChangeListener 决定该值是否有效,如果无效,则将 JSlider 值设置为“合法”值(确实存在)。但是,拇指不反映该值,而是反映用户移动拇指的值..
  • 不如你让它成为现实,而不是“实际上”,毕竟,管理这些事情是BoundedRangeModels 的责任,而你目前的方法似乎行不通
  • @MadProgrammer 使用基于 DefaultBoundedRangeModel 的自定义模型我仍然没有成功。覆盖 setValue() 并拦截非法尝试值并替换合法值不起作用。即使为了确定而强制调用 fireStateChanged() 也是如此。我看不到源(在 Mac 上工作,Aqua LaF),但症状让我怀疑尽管模型中设置了值,但鼠标释放总是设置拇指位置。

标签: java swing jslider changelistener


【解决方案1】:

您也许可以覆盖MetalSliderUIcreateTrackListener(...) 方法以防止拖拽。

编辑

  • 另一种选择是使用JLayer(未经测试的代码,可能需要一些自定义才能适用于其他LookAndFeel):
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.BasicSliderUI;
// import javax.swing.plaf.metal.MetalSliderUI;
// import javax.swing.plaf.synth.SynthSliderUI;
// import com.sun.java.swing.plaf.windows.WindowsSliderUI;

public class DragLimitedSliderTest {
  private static int MAXI = 80;
  private JComponent makeUI() {
    JSlider slider1 = makeSlider();
    JSlider slider2 = makeSlider();
    slider2.setUI(new BasicSliderUI(slider2) {
    //slider2.setUI(new WindowsSliderUI(slider2) {
    //slider2.setUI(new MetalSliderUI() {
    //slider2.setUI(new SynthSliderUI(slider2) {
      @Override protected TrackListener createTrackListener(JSlider slider) {
        return new TrackListener() {
          @Override public void mouseDragged(MouseEvent e) {
            //case HORIZONTAL:
            int halfThumbWidth = thumbRect.width / 2;
            int thumbLeft = e.getX() - offset;
            int maxPos = xPositionForValue(MAXI) - halfThumbWidth;
            if (thumbLeft > maxPos) {
              int x = maxPos + offset;
              MouseEvent me = new MouseEvent(
                e.getComponent(), e.getID(), e.getWhen(), e.getModifiers(),
                x, e.getY(),
                e.getXOnScreen(), e.getYOnScreen(),
                e.getClickCount(), e.isPopupTrigger(), e.getButton());
              e.consume();
              super.mouseDragged(me);
            } else {
              super.mouseDragged(e);
            }
          }
        };
      }
    });
    JSlider slider3 = makeSlider();

    JPanel p = new JPanel(new GridLayout(3, 1));
    p.add(slider1);
    p.add(slider2);
    p.add(new JLayer<JSlider>(slider3, new DisableInputLayerUI()));
    return p;
  }
  private static JSlider makeSlider() {
    JSlider slider = new JSlider(0, 100, 40) {
      @Override public void setValue(int n) {
        super.setValue(n);
      }
    };
    slider.setMajorTickSpacing(10);
    slider.setPaintTicks(true);
    slider.setPaintLabels(true);
    Dictionary dictionary = slider.getLabelTable();
    if (dictionary != null) {
      Enumeration elements = dictionary.elements();
      while (elements.hasMoreElements()) {
        JLabel label = (JLabel) elements.nextElement();
        int v = Integer.parseInt(label.getText());
        if (v > MAXI) {
          label.setForeground(Color.RED);
        }
      }
    }
    slider.getModel().addChangeListener(new ChangeListener() {
      @Override public void stateChanged(ChangeEvent e) {
        BoundedRangeModel m = (BoundedRangeModel) e.getSource();
        if (m.getValue() > MAXI) {
          m.setValue(MAXI);
        }
      }
    });
    return slider;
  }
  public static void main(String... args) {
    EventQueue.invokeLater(() -> {
      try {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
//         for (UIManager.LookAndFeelInfo laf: UIManager.getInstalledLookAndFeels()) {
//           if ("Nimbus".equals(laf.getName())) {
//             UIManager.setLookAndFeel(laf.getClassName());
//           }
//         }
      } catch (Exception e) {
        e.printStackTrace();
      }
      JFrame f = new JFrame();
      f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
      f.getContentPane().add(new DragLimitedSliderTest().makeUI());
      f.setSize(320, 240);
      f.setLocationRelativeTo(null);
      f.setVisible(true);
    });
  }
}

class DisableInputLayerUI extends LayerUI<JSlider> {
  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      JLayer jlayer = (JLayer) c;
      jlayer.setLayerEventMask(
        AWTEvent.MOUSE_EVENT_MASK |
        AWTEvent.MOUSE_MOTION_EVENT_MASK);
    }
  }
  @Override public void uninstallUI(JComponent c) {
    if (c instanceof JLayer) {
      JLayer jlayer = (JLayer) c;
      jlayer.setLayerEventMask(0);
    }
    super.uninstallUI(c);
  }
  private Rectangle thumbRect = new Rectangle(11, 19); //magic number
  private Rectangle focusRect = new Rectangle();
  private Rectangle contentRect = new Rectangle();
  private Rectangle trackRect = new Rectangle();
  private int offset;

  protected int xPositionForValue(JSlider slider, int value) {
    int min = slider.getMinimum();
    int max = slider.getMaximum();
    int trackLength = trackRect.width;
    double valueRange = (double) max - (double) min;
    double pixelsPerValue = (double) trackLength / valueRange;
    int trackLeft = trackRect.x;
    int trackRight = trackRect.x + (trackRect.width - 1);
    int xPosition;

    xPosition = trackLeft;
    xPosition += Math.round(pixelsPerValue * ((double) value - min));

    xPosition = Math.max(trackLeft, xPosition);
    xPosition = Math.min(trackRight, xPosition);

    return xPosition;
  }
  protected int getHeightOfTallestLabel(JSlider slider) {
    Dictionary dictionary = slider.getLabelTable();
    int tallest = 0;
    if (dictionary != null) {
      Enumeration keys = dictionary.keys();
      while (keys.hasMoreElements()) {
        JComponent label = (JComponent) dictionary.get(keys.nextElement());
        tallest = Math.max(label.getPreferredSize().height, tallest);
      }
    }
    return tallest;
  }
  @Override protected void processMouseEvent(MouseEvent e, JLayer<? extends JSlider> l) {
    JSlider slider = l.getView();
    if (e.getID() == MouseEvent.MOUSE_PRESSED) {
      //case HORIZONTAL:

      //recalculateIfInsetsChanged()
      Insets insetCache = slider.getInsets();
      Insets focusInsets = UIManager.getInsets("Slider.focusInsets");
      if (focusInsets == null) {
        focusInsets = new Insets(2, 2, 2, 2); //magic number
      }

      //calculateFocusRect()
      focusRect.x = insetCache.left;
      focusRect.y = insetCache.top;
      focusRect.width = slider.getWidth() - (insetCache.left + insetCache.right);
      focusRect.height = slider.getHeight() - (insetCache.top + insetCache.bottom);

      //calculateContentRect()
      contentRect.x = focusRect.x + focusInsets.left;
      contentRect.y = focusRect.y + focusInsets.top;
      contentRect.width = focusRect.width - (focusInsets.left + focusInsets.right);
      contentRect.height = focusRect.height - (focusInsets.top + focusInsets.bottom);

      //calculateThumbSize()
      Icon ti = UIManager.getIcon("Slider.horizontalThumbIcon");
      if (ti != null) {
        thumbRect.width = ti.getIconWidth();
        thumbRect.height = ti.getIconHeight();
      }

      //calculateTrackBuffer()
      int trackBuffer = 9; //magic number, Windows: 9, Metal: 10 ...

      //calculateTrackRect()
      int centerSpacing = thumbRect.height;
      if (slider.getPaintTicks())  centerSpacing += 8; //magic number getTickLength();
      if (slider.getPaintLabels()) centerSpacing += getHeightOfTallestLabel(slider);
      trackRect.x = contentRect.x + trackBuffer;
      trackRect.y = contentRect.y + (contentRect.height - centerSpacing - 1) / 2;
      trackRect.width = contentRect.width - (trackBuffer * 2);
      trackRect.height = thumbRect.height;

      //calculateThumbLocation()
      int valuePosition = xPositionForValue(slider, slider.getValue());
      thumbRect.x = valuePosition - (thumbRect.width / 2);
      thumbRect.y = trackRect.y;
      offset = e.getX() - thumbRect.x;
    }
  }
  @Override protected void processMouseMotionEvent(MouseEvent e, JLayer<? extends JSlider> l) {
    if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
      JSlider slider = l.getView();
      //case HORIZONTAL:
      int halfThumbWidth = thumbRect.width / 2;
      int thumbLeft = e.getX() - offset;
      int maxPos = xPositionForValue(slider, 80) - halfThumbWidth;
      if (thumbLeft > maxPos) {
        e.consume();
        SliderUI ui = slider.getUI();
        if (ui instanceof BasicSliderUI) {
          ((BasicSliderUI) ui).setThumbLocation(maxPos, thumbRect.y);
        }
        slider.getModel().setValue(80);
      }
    }
  }
}

【讨论】:

  • 我看到这种方法的问题是我现在被锁定在一个特定的 LaF 中。我的应用程序在 Windows、Mac、Linux 上运行。只是试图测试这种方法,我遇到了 Eclipse 抱怨试图在未经许可的情况下访问非公共 API 的问题。覆盖 TrackListener 似乎是一种方式,但这不是我能找到的公共 API 的一部分。
  • 虽然重写 TrackListener 似乎是一种解决方案,但当 SliderUI 实现类未知时,我还没有找到在实践中使用它的方法。我想我可以在运行时确定类,如果它是我知道的(而不是最终的)覆盖 TrackListener 就像你的例子一样。有没有其他办法?
【解决方案2】:

似乎真的没有办法以一种在不同的外观实现中保持一致的方式来做我想做的事情。跟踪 JSlider、BasicSliderUI 和 BoundedRangeModel 的通知和侦听器,结果发现无法通过公共方法强制重新计算拇指位置,并且基本侦听器实现特别防止在拖动或终止拖动时发生这种情况(鼠标释放)。有关更多信息,请参阅原始问题的更新。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-05-30
    • 1970-01-01
    • 2023-04-02
    • 2021-07-26
    • 2014-07-24
    • 1970-01-01
    • 1970-01-01
    • 2010-10-28
    相关资源
    最近更新 更多