Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
551 views
in Technique[技术] by (71.8m points)

swing - Java JSlider precision problems

I have a list of N JSliders (N does not change procedurally, only as I add more features. Currently N equals 4). The sum of all the sliders values must equal to 100. As one slider moves the rest of the sliders shall adjust. Each slider has values that range from 0 to 100.

Currently I am using this logic when a slider is changed (pseudo-code):

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

The problem with this setup is slider's values are stored as ints. So as I adjust the sliders I get precision problems: sliders will twitch or not move at all depending on the ratio value. Also the total value does not always add up to 100.

Does anyone have a solution to this problem without creating an entirely new JSlider class that supports floats or doubles?

If you want an example of the behavior I want, visit: Humble Indie Bundle and scroll to the bottom of the page.

thank you

p.s. Multiplying the values by the ratio allows for the user to 'lock' values at 0. However, I am not sure what to do when 3 of the 4 sliders are at 0 and the 4th slider is at 100 and I move the 4th slider down. Using the logic above, the 3 sliders with 0 as their value stay put and the 4th slider moves to where the user puts it, which makes the total less than 100, which is improper behavior.

EDIT

Here is the 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);
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Why not making the granularity of the JSlider models finer by say having them go from 0 to 1000000, and having the sum be 1000000? With the proper Dictionary for the LabelTable, the user will probably not know that it doesn't go from 0 to 100.

For example:

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;
         }
      }

   }

}

Edited to have SliderGroup2 use a List of BoundedRangeModels rather than JSliders.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...