4

我有一个 N JSlider 的列表(N 在程序上不会改变,只有当我添加更多功能时。目前 N 等于 4)。所有滑块值的总和必须等于 100。当一个滑块移动时,其余滑块应进行调整。每个滑块的值范围从 0 到 100。

目前我在更改滑块时使用此逻辑(伪代码):

newValue = currentSlider.getValue

otherSliders = allSliders sans currentSlider
othersValue = summation of otherSliders values
properOthersValue = 100 - newValue

ratio = properOthersValue / othersValue

for slider in  otherSlider 
    slider.value = slider.getValue * ratio

此设置的问题是滑块的值存储为整数。因此,当我调整滑块时,我会遇到精度问题:滑块会根据比率值抽动或根本不移动。此外,总值并不总是等于 100。

有没有人在不创建支持浮点数或双精度数的全新 JSlider 类的情况下解决这个问题?

如果您想要我想要的行为示例,请访问:Humble Indie Bundle并滚动到页面底部。

谢谢你

ps 将值乘以比率允许用户将值“锁定”为 0。但是,当 4 个滑块中的 3 个为 0 且第 4 个滑块为 100 并且我移动第 4 个滑块时,我不确定该怎么做向下。使用上面的逻辑,以 0 为值的 3 个滑块保持不变,第 4 个滑块移动到用户放置的位置,这使得总数小于 100,这是不正确的行为。

编辑

这是SSCCE:

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.util.LinkedList;

public class SliderDemo
{
    static LinkedList<JSlider> sliders = new LinkedList<JSlider>();

    static class SliderListener implements ChangeListener
    {
        boolean updating = false;

        public void stateChanged(ChangeEvent e)
        {
            if (updating) return;
            updating = true;

            JSlider source = (JSlider)e.getSource();

            int newValue = source.getValue();
            LinkedList<JSlider> otherSliders = new LinkedList<JSlider>(sliders);
            otherSliders.remove(source);

            int otherValue = 0;
            for (JSlider slider : otherSliders)
            {
                otherValue += slider.getValue();
            }

            int properValue = 100 - newValue;
            double ratio = properValue / (double)otherValue;

            for (JSlider slider : otherSliders)
            {
                int currentValue = slider.getValue();
                int updatedValue = (int) (currentValue * ratio);
                slider.setValue(updatedValue);
            }

            int total = 0;
            for (JSlider slider : sliders)
            {
                total += slider.getValue();
            }
            System.out.println("Total = " + total);

            updating = false;
        }
    }

    public static void main(String[] args)
    {
        JFrame frame = new JFrame("SliderDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Container container = frame.getContentPane();
        JPanel sliderPanel = new JPanel(new GridBagLayout());
        container.add(sliderPanel);

        SliderListener listener = new SliderListener();

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 0;
        int sliderCount = 4;
        int initial = 100 / sliderCount;
        for (int i = 0; i < sliderCount; i++)
        {
            gbc.gridy = i;
            JSlider slider = new JSlider(0, 100, initial);
            slider.addChangeListener(listener);
            slider.setMajorTickSpacing(50);
            slider.setPaintTicks(true);
            sliders.add(slider);
            sliderPanel.add(slider, gbc);
        }

        frame.pack();
        frame.setVisible(true);
    }
}
4

3 回答 3

6

为什么不通过让 JSlider 模型的粒度从 0 变为 1000000 并且总和为 1000000 来使它们的粒度更细呢?使用正确Dictionary的 LabelTable,用户可能不知道它不会从 0 变为 100。

例如:

import java.awt.Dimension;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

@SuppressWarnings("serial")
public class LinkedSliders2 extends JPanel {
   private static final int SLIDER_COUNT = 5;
   public static final int SLIDER_MAX_VALUE = 1000;
   private static final int MAJOR_TICK_DIVISIONS = 5;
   private static final int MINOR_TICK_DIVISIONS = 20;
   private static final int LS_WIDTH = 700;
   private static final int LS_HEIGHT = 500;
   private JSlider[] sliders = new JSlider[SLIDER_COUNT];
   private SliderGroup2 sliderGroup = new SliderGroup2(SLIDER_MAX_VALUE);

   public LinkedSliders2() {
      Dictionary<Integer, JComponent> myDictionary = new Hashtable<Integer, JComponent>();
      for (int i = 0; i <= MAJOR_TICK_DIVISIONS; i++) {
         Integer key = i * SLIDER_MAX_VALUE / MAJOR_TICK_DIVISIONS;
         JLabel value = new JLabel(String.valueOf(i * 100 / MAJOR_TICK_DIVISIONS));
         myDictionary.put(key, value);
      }
      setLayout(new GridLayout(0, 1));
      for (int i = 0; i < sliders.length; i++) {
         sliders[i] = new JSlider(0, SLIDER_MAX_VALUE, SLIDER_MAX_VALUE
               / SLIDER_COUNT);
         sliders[i].setLabelTable(myDictionary );
         sliders[i].setMajorTickSpacing(SLIDER_MAX_VALUE / MAJOR_TICK_DIVISIONS);
         sliders[i].setMinorTickSpacing(SLIDER_MAX_VALUE / MINOR_TICK_DIVISIONS);
         sliders[i].setPaintLabels(true);
         sliders[i].setPaintTicks(true);
         sliders[i].setPaintTrack(true);
         sliderGroup.addSlider(sliders[i]);
         add(sliders[i]);
      }
   }

   @Override
   public Dimension getPreferredSize() {
      return new Dimension(LS_WIDTH, LS_HEIGHT);
   }

   private static void createAndShowGui() {
      LinkedSliders2 mainPanel = new LinkedSliders2();

      JFrame frame = new JFrame("LinkedSliders");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

class SliderGroup2 {
   private List<BoundedRangeModel> sliderModelList = new ArrayList<BoundedRangeModel>();
   private ChangeListener changeListener = new SliderModelListener();
   private int maxValueSum;

   public SliderGroup2(int maxValueSum) {
      this.maxValueSum = maxValueSum;
   }

   public void addSlider(JSlider slider) {
      BoundedRangeModel model = slider.getModel();
      sliderModelList.add(model);
      model.addChangeListener(changeListener);
   }

   private class SliderModelListener implements ChangeListener {
      private boolean internalChange = false;

      @Override
      public void stateChanged(ChangeEvent cEvt) {
         if (!internalChange) {
            internalChange = true;
            BoundedRangeModel sourceModel = (BoundedRangeModel) cEvt.getSource();
            int sourceValue = sourceModel.getValue();

            int oldSumOfOtherSliders = 0;
            for (BoundedRangeModel model : sliderModelList) {
               if (model != sourceModel) {
                  oldSumOfOtherSliders += model.getValue();
               }
            }
            if (oldSumOfOtherSliders == 0) {
               for (BoundedRangeModel model : sliderModelList) {
                  if (model != sourceModel) {
                     model.setValue(1);
                  }
               }
               internalChange = false;
               return;
            }

            int newSumOfOtherSliders = maxValueSum - sourceValue;

            for (BoundedRangeModel model : sliderModelList) {
               if (model != sourceModel) {
                  long newValue = ((long) newSumOfOtherSliders * model
                        .getValue()) / oldSumOfOtherSliders;
                  model.setValue((int) newValue);
               }
            }

            int total = 0;
            for (BoundedRangeModel model : sliderModelList) {
               total += model.getValue();
            }
            //!! System.out.printf("Total = %.0f%n", (double)total * 100 / LinkedSliders2.SLIDER_MAX_VALUE);

            internalChange = false;
         }
      }

   }

}

编辑让 SliderGroup2 使用 BoundedRangeModels 列表而不是 JSlider。

于 2011-10-01T02:45:42.290 回答
2

滑块将根据比率值抽动或根本不移动。

HumbleBundle 也有同样的问题。如果您通过键盘移动滑块,则更改仅为 1,这意味着它只会转到第一个滑块。所以你的比率最终会不同步。

此外,总值并不总是等于 100。

因此,您需要进行四舍五入检查。如果它没有增加到 100,那么您需要确定错误的位置。考虑到上述问题,可能是最后一个滑块?

当 4 个滑块中的 3 个为 0 且第 4 个滑块为 100 并且我将第 4 个滑块向下移动时,我不确定该怎么做。

HumbleBundle 处理它的方式是移动所有切片器。但是,它只允许您将滑块向下移动 3 个增量,以便您可以将 3 个滑块中的每一个都增加 1。

甚至 HumbleBundle 的实现也不完美。

于 2011-10-01T02:11:06.173 回答
2

借鉴了一些 Hovercrafts 解决方案,我想出了一种不同的方法。这种方法的基础是在移动滑块时跟踪“其他滑块”值。只要您继续滑动相同的滑块,冻结的值就会用于计算新值。然后将任何舍入差异依次应用于每个滑块,直到用完差异。使用这种方法,您可以将滑块的增量更改均匀地应用于所有其他滑块。

模型中的值是滑块的实际值,您也可以使用键盘调整滑块:

import java.awt.*;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.List;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;


public class SliderGroup implements ChangeListener
{
    private List<JSlider> sliders = new ArrayList<JSlider>();
    private int groupSum;

    private boolean internalChange = false;
    private JSlider previousSlider;
    private List<SliderInfo> otherSliders = new ArrayList<SliderInfo>();

    public SliderGroup(int groupSum)
    {
        this.groupSum = groupSum;
    }

    public void addSlider(JSlider slider)
    {
        sliders.add(slider);
        slider.addChangeListener(this);
    }

    @Override
    public void stateChanged(ChangeEvent e)
    {
        if (internalChange) return;

        internalChange = true;
        JSlider sourceSlider = (JSlider)e.getSource();

        if (previousSlider != sourceSlider)
        {
            setupForSliding(sourceSlider);
            previousSlider = sourceSlider;
        }

        int newSumOfOtherSliders = groupSum - sourceSlider.getValue();
        int oldSumOfOtherSliders = 0;

        for (SliderInfo info : otherSliders)
        {
            JSlider slider = info.getSlider();

            if (slider != sourceSlider)
            {
                oldSumOfOtherSliders += info.getValue();
            }
        }

        int difference = newSumOfOtherSliders - oldSumOfOtherSliders;

        if (oldSumOfOtherSliders == 0)
        {
            resetOtherSliders( difference / otherSliders.size() );
            allocateDifference(difference % otherSliders.size(), true);
            internalChange = false;
            return;
        }

        double ratio = (double)newSumOfOtherSliders / oldSumOfOtherSliders;

        for (SliderInfo info : otherSliders)
        {
                JSlider slider = info.getSlider();
                int oldValue = info.getValue();
                int newValue = (int)Math.round(oldValue * ratio);
                difference += oldValue - newValue;
                slider.getModel().setValue( newValue );
        }

        if (difference != 0)
        {
            allocateDifference(difference, false);
        }

        internalChange = false;
    }

    private void allocateDifference(int difference, boolean adjustZeroValue)
    {
        while (difference != 0)
        {
            for (SliderInfo info : otherSliders)
            {
                if (info.getValue() != 0 || adjustZeroValue)
                {
                    JSlider slider = info.getSlider();

                    if (difference > 0)
                    {
                        slider.getModel().setValue( slider.getValue() + 1 );
                        difference--;
                    }

                    if (difference < 0)
                    {
                        slider.getModel().setValue( slider.getValue() - 1 );
                        difference++;
                    }
                }
            }
        }
    }

    private void resetOtherSliders(int resetValue)
    {
        for (SliderInfo info : otherSliders)
        {
            JSlider slider = info.getSlider();
            slider.getModel().setValue( resetValue );
        }
    }

    private void setupForSliding(JSlider sourceSlider)
    {
        otherSliders.clear();

        for (JSlider slider: sliders)
        {
            if (slider != sourceSlider)
            {
                otherSliders.add( new SliderInfo(slider, slider.getValue() ) );
            }
        }
    }

    class SliderInfo
    {
        private JSlider slider;
        private int value;

        public SliderInfo(JSlider slider, int value)
        {
            this.slider = slider;
            this.value = value;
        }

        public JSlider getSlider()
        {
            return slider;
        }

        public int getValue()
        {
            return value;
        }
    }


    private static JPanel createSliderPanel(int groupSum, int sliderCount)
    {
        int sliderValue = groupSum / sliderCount;

        SliderGroup sg = new SliderGroup(groupSum);

        JPanel panel = new JPanel( new BorderLayout() );

        JPanel sliderPanel = new JPanel( new GridLayout(0, 1) );
        panel.add(sliderPanel, BorderLayout.CENTER);

        JPanel labelPanel = new JPanel( new GridLayout(0, 1) );
        panel.add(labelPanel, BorderLayout.EAST);

        for (int i = 0; i < sliderCount; i++)
        {
            JLabel label = new JLabel();
            label.setText( Integer.toString(sliderValue) );
            labelPanel.add( label );

            JSlider slider = new JSlider(0, groupSum, sliderValue);
            slider.setMajorTickSpacing(25);
            slider.setMinorTickSpacing(5);
            slider.setPaintTicks(true);
            slider.setPaintLabels(true);
            slider.setPaintTrack(true);
            slider.addChangeListener( new LabelChangeListener(label) );
            sliderPanel.add( slider );

            sg.addSlider( slider );
        }

        return panel;
    }

    static class LabelChangeListener implements ChangeListener
    {
        private JLabel label;

        public LabelChangeListener(JLabel label)
        {
            this.label = label;
        }

        @Override
        public void stateChanged(ChangeEvent e)
        {
            JSlider slider = (JSlider)e.getSource();
            label.setText( Integer.toString(slider.getValue()) );
        }
    }

    private static void createAndShowGui()
    {
        JPanel panel = createSliderPanel(100, 5);

        JFrame frame = new JFrame("SliderGroup");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(panel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGui();
            }
        });
    }
}
于 2011-10-02T00:57:53.947 回答