Search code examples
javafxsliderjavafx-8

JavaFX Slider : How to drag the thumb only by increments


I am trying to implement the Slider such that user can drag only by given increments. I tried in different ways by using the Slider API, but didnt get the desired results. Below is a quick demo of what I had tried. I am expecting to drag the thumb only in increments of 10 not with intermediate values. snapToTicks is doing what I required, but only after finishing the drag. I am trying to not move the thumb till the next desired block increment is reached.

Can anyone let me know how can i achieve this. Below is the screenshot while dragging.

enter image description here

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class SliderDemo extends Application {

    public static void main(String... args){
        Application.launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {

        Label label = new Label();
        label.setStyle("-fx-font-size:30px");

        Slider slider = new Slider(5,240,5);
        slider.setBlockIncrement(10);
        slider.setMajorTickUnit(10);
        slider.setMinorTickCount(0);
        slider.setShowTickLabels(true);
        slider.setShowTickMarks(true);
        slider.setSnapToTicks(true);

        slider.valueProperty().addListener((obs,old,val)->label.setText((int)Math.round(val.doubleValue())+""));
        VBox root = new VBox(slider,label);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(20));
        root.setSpacing(20);

        Scene scene = new Scene(root,600,200);
        stage.setScene(scene);
        stage.show();
    }
}

Solution

  • The solution is to set the value of the slider directly inside of the listener. The listener will not be called again

    final ChangeListener<Number> numberChangeListener = (obs, old, val) -> {
        final double roundedValue = Math.floor(val.doubleValue() / 10.0) * 10.0;
        slider.valueProperty().set(roundedValue);
        label.setText(Double.toString(roundedValue));
    };
    
    slider.valueProperty().addListener(numberChangeListener);
    

    If you use Math.floor() instead of round you get a more intuatuive behavior of the thumb.