tags:

views:

213

answers:

1

I have a JSlider with 65536 different values. It works great for coarse adjustments and for very fine adjustments (+/-1 using up/down arrow) but is very poor in the middle.

Is there anything out there that would be better? I can vaguely imagine taking 2 sliders one for coarse + fine adjustments, but can't really figure out how to get them to work together.

+1  A: 

What about using a JSpinner instead of a JSlider? With a SpinnerNumberModel, you can set the step size and even change the step size dynamically.

If you're OK with having multiple controls, you could even have two spinners, one for setting your values and another for setting the step size that is used by the first spinner.

For an example of this, I took the SliderDemo code from the Swing slider tutorial and modified it instead to use two JSpinners instead of a single JSlider. Here's the most interesting part of the code that I changed:

//Create the slider^H^H^H^H^H^H spinners.
// JSlider framesPerSecond = new JSlider(JSlider.HORIZONTAL,
//                                       FPS_MIN, FPS_MAX, FPS_INIT);
final int initStep = 1;
final SpinnerNumberModel animationModel = new SpinnerNumberModel(FPS_INIT,
                                                                 FPS_MIN,
                                                                 FPS_MAX,
                                                                 initStep);
final SpinnerNumberModel stepSizeModel = new SpinnerNumberModel(initStep,
                                                                1,
                                                                10,
                                                                1);
final JSpinner framesSpinner = new JSpinner(animationModel);
framesSpinner.addChangeListener(this);

final JSpinner stepSpinner = new JSpinner(stepSizeModel);
stepSpinner.addChangeListener(new ChangeListener() 
{
    public void stateChanged(ChangeEvent arg0)
    {
        animationModel.setStepSize(stepSizeModel.getNumber());
    } 
});

I also had to make a bunch of less interesting changes, such as creating a label for the step size spinner, adding the new label and new spinner to the container, and changing the stateChanged() method on this to cast the source of the event to a JSpinner instead of casting it to a JSlider.

You could, of course, elaborate on this further, such as increasing the step size for the step size spinner (for example, so that you can change the step size from 1 to 101 in a single click). You could also use a different control instead of a JSpinner to set the step size, such as a combo box.

Finally, to make this all really easy to use, you would likely want to hook up some keystroke accelerators (possibly through a menu?) so that you could change the step size without actually moving the mouse or the keyboard focus from one spinner to another.

Edit: Given that you have to use a JSlider no matter what, are you aware that you can use PgUp/PgDn to move up and down by 1/10th of the total range?

If you want to change that 1/10th amount (such as making it dynamic), then you'll need to override the the method BasicSliderUI.scrollByBlock().

Here's an example where I just overrode the UI class of a JSlider to step by 1/4th of the range, instead of 1/10th:

//Create the slider.
JSlider framesPerSecond = new JSlider(JSlider.HORIZONTAL,
                                      FPS_MIN, FPS_MAX, FPS_INIT);
framesPerSecond.setUI(new javax.swing.plaf.metal.MetalSliderUI() {

    private static final int SLIDER_FRACTION = 4;

    /**
     * This code is cut, paste, and modified from
     * {@link javax.swing.plaf.basic.BasicSliderUI#scrollByBlock(int).
     * I should be ashamed of cutting and pasting, but whoever hardcoded the magic
     * number "10" in the original code should be more ashamed than me. ;-) 
     *
     * @param direction
     *          either +1 or -1
     */   
    @Override
    public void scrollByBlock(final int direction) {
        synchronized(slider)    {
            int oldValue = slider.getValue();
            int blockIncrement = (slider.getMaximum() - slider.getMinimum()) / SLIDER_FRACTION;
            if (blockIncrement <= 0 && slider.getMaximum() > slider.getMinimum()) {
                blockIncrement = 1;
            }
            int delta = blockIncrement * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL);
            slider.setValue(oldValue + delta);          
        }
    }
});

From here, it wouldn't be too hard to replace that constant SLIDER_FRACTION with a variable that was set by another slider or by a spinner, would it?

Joe Carnahan
thanks, I like spinners too, but I need a slider in this application. Maybe a spinner + slider combination would be good.
Jason S
If you absolutely have to use a slider, then maybe you can get the desired behavior by modifying the tick spacing dynamically and turning on snap-to-tick behavior? I'm not sure how that interacts with using the arrow keys on a slider, but it's worth investigating.
Joe Carnahan
OK, I figured out a way to do this just with a `JSlider`, and I edited my answer accordingly. Thank you for challenging me to find an even better solution. :-)
Joe Carnahan
cool, I'll try it!
Jason S