1

I have this issue where the JavaFX Slider doesn't invoke the ChangeListener for the valueProperty when the slider-knob is on the min or max extreme. I want to execute some code only after the slider value has been changed (not while the slider is being dragged). I tried to achieve this with the following code:

Slider slider = new Slider();
slider.setMin(0);
slider.setMax(10);
slider.setMajorTickUnit(5);
slider.setMinorTickCount(5);
slider.setBlockIncrement(1);
slider.setSnapToTicks(true);
slider.setShowTickMarks(true);
slider.setShowTickLabels(true);

ChangeListener<? super Number> valueListener = (observable, oldValue, newValue) -> {
    if (!slider.isValueChanging()) {
        System.out.println("Value changed");
    }
};

slider.valueProperty().addListener(valueListener);

You'll see that when you drag and release the slider the string Value changed is printed out every time except for when the slider-knob is released on the 0 or 10 positions. Is this the expected behaviour? Or am I missing something?

wcmatthysen
  • 445
  • 4
  • 19

2 Answers2

3

The problem is a consequence of the design of fx properties: in contrast to the ol' bean spec, they are completely self-contained, each fires on change independently of the other. A consequence is that listeners to one property must not query any other property when reacting to a change.

Applied to the context of Slider's value/isValueAdjusting properties:

  • when listening to value, the adjusting state is unspecified
  • when listening to adjusting, the value state is unspecified

At the end of the day, Slider's api is simply too weak to serve the common use-case (of reacting to changes only if they are completed) without considerable effort.

A way out might be a custom slider with enhanced api. The outline below is based on the observation (beware: implementation detail!) that all internals set the final value via slider.adjustValue(value). The enhanced slider

  • adds a property adjustedValue that's updated when the change is final
  • updates the adjustedValue at the end of adjustValue

The outline (incomplete and not formally tested):

public static class AdjustedSlider extends Slider {

    private DoubleProperty adjustedValue;

    public AdjustedSlider() {
        super();
    }

    public AdjustedSlider(double min, double max, double value) {
        super(min, max, value);
    }

    public DoubleProperty adjustedValueProperty() {
        if (adjustedValue == null) {
            adjustedValue = new SimpleDoubleProperty(this, "adjustedValue", 0);
        }
        return adjustedValue;
    }

    public double getAdjustedValue() {
        return adjustedValueProperty().get();
    }

    private void setAdjustedValue(double value) {
        adjustedValueProperty().set(value);
    }

    @Override
    public void adjustValue(double newValue) {
        super.adjustValue(newValue);
        setAdjustedValue(getValue());
    }

}
kleopatra
  • 51,061
  • 28
  • 99
  • 211
1

Looks like there is already a solution posted here. It basically comes down to adding explicit checks for the extremes so that the ChangeListener looks like this:

ChangeListener<? super Number> valueListener = (observable, oldValue, newValue) -> {
    boolean isOnMin = newValue.doubleValue() == slider.getMin();
    boolean isOnMax = newValue.doubleValue() == slider.getMax();
    if (!slider.isValueChanging() || isOnMin || isOnMax) {
        System.out.println("Value changed");
    }
};

However, the downside to this solution is that it immediately invokes the System.out.println whenever the slider touches either one of the edges, even though the mouse-button has not been released yet.

An alternative solution that I found was to keep the original ChangeListener that only checks the isValueChanging-value, but to add the following ChangeListener to the valueChangingProperty:

 ChangeListener<? super Boolean> valueChangingListener = (observable, oldUpdating, newUpdating) -> {
    double value = slider.getValue();
    boolean notUpdatingAnymore = oldUpdating && !newUpdating;
    boolean isOnExtreme = value == slider.getMin() || value == slider.getMax();
    if (notUpdatingAnymore && isOnExtreme) {
        System.out.println("Value changed");
    }
};

slider.valueChangingProperty().addListener(valueChangingListener);

It's ugly, but it works. I don't know if there is a cleaner way to achieve the desired behaviour.

wcmatthysen
  • 445
  • 4
  • 19
  • thought it was fixed - but obviously not. See also: https://stackoverflow.com/q/35129670/203657 as to why that happens – kleopatra Jul 20 '18 at 09:27